From 0dfbd44c72e240083a871a6f522bcaf0e217d839 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Mon, 4 Apr 2016 18:39:00 +1000 Subject: [PATCH] Faster compression Using java's inbuilt compression algorithms (gzip or deflator both) was a bit too slow for me. Fawe is now using the LZ4 compression algorithm, which is super fast. So fast that I decided to add a default 1 pass of compression to history. Enabling further compression will do an additional high LZ4 compression. --- build.gradle | 1 - bukkit/build/resources/main/plugin.yml | 5 + .../com/boydti/fawe/bukkit/BukkitCommand.java | 2 +- bukkit/src/main/resources/plugin.yml | 5 + .../main/darwin/x86_64/liblz4-java.dylib | Bin 0 -> 47188 bytes .../resources/main/linux/amd64/liblz4-java.so | Bin 0 -> 59545 bytes .../resources/main/linux/i386/liblz4-java.so | Bin 0 -> 68840 bytes .../resources/main/win32/amd64/liblz4-java.so | Bin 0 -> 161360 bytes core/src/main/java/com/boydti/fawe/Fawe.java | 10 +- .../java/com/boydti/fawe/command/Reload.java | 19 + .../java/com/boydti/fawe/config/Settings.java | 8 +- .../object/changeset/DiskStorageHistory.java | 22 +- .../changeset/MemoryOptimizedHistory.java | 76 ++- core/src/main/java/com/test.das | 0 .../net/jpountz/lz4/LZ4BlockInputStream.java | 248 ++++++++ .../net/jpountz/lz4/LZ4BlockOutputStream.java | 259 +++++++++ .../net/jpountz/lz4/LZ4ByteBufferUtils.java | 237 ++++++++ .../java/net/jpountz/lz4/LZ4Compressor.java | 126 ++++ .../java/net/jpountz/lz4/LZ4Constants.java | 53 ++ .../java/net/jpountz/lz4/LZ4Decompressor.java | 25 + .../java/net/jpountz/lz4/LZ4Exception.java | 36 ++ .../main/java/net/jpountz/lz4/LZ4Factory.java | 258 ++++++++ .../net/jpountz/lz4/LZ4FastDecompressor.java | 106 ++++ .../net/jpountz/lz4/LZ4HCJNICompressor.java | 88 +++ .../jpountz/lz4/LZ4HCJavaSafeCompressor.java | 550 ++++++++++++++++++ .../lz4/LZ4HCJavaUnsafeCompressor.java | 550 ++++++++++++++++++ .../java/net/jpountz/lz4/LZ4InputStream.java | 133 +++++ .../src/main/java/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 +++++++ .../java/net/jpountz/lz4/LZ4OutputStream.java | 73 +++ .../net/jpountz/lz4/LZ4SafeDecompressor.java | 117 ++++ .../java/net/jpountz/lz4/LZ4SafeUtils.java | 179 ++++++ .../java/net/jpountz/lz4/LZ4StreamHelper.java | 36 ++ .../java/net/jpountz/lz4/LZ4StreamTest.java | 150 +++++ .../lz4/LZ4UnknownSizeDecompressor.java | 27 + .../main/java/net/jpountz/lz4/LZ4Utils.java | 73 +++ .../main/java/net/jpountz/lz4/package.html | 55 ++ .../net/jpountz/util/ByteBufferUtils.java | 92 +++ .../java/net/jpountz/util/LZ4UnsafeUtils.java | 206 +++++++ .../main/java/net/jpountz/util/Native.java | 125 ++++ .../main/java/net/jpountz/util/SafeUtils.java | 95 +++ .../java/net/jpountz/util/UnsafeUtils.java | 147 +++++ .../src/main/java/net/jpountz/util/Utils.java | 35 ++ .../main/java/net/jpountz/util/package.html | 22 + .../xxhash/AbstractStreamingXXHash32Java.java | 42 ++ .../xxhash/AbstractStreamingXXHash64Java.java | 43 ++ .../net/jpountz/xxhash/StreamingXXHash32.java | 111 ++++ .../jpountz/xxhash/StreamingXXHash32JNI.java | 71 +++ .../net/jpountz/xxhash/StreamingXXHash64.java | 111 ++++ .../jpountz/xxhash/StreamingXXHash64JNI.java | 71 +++ .../java/net/jpountz/xxhash/XXHash32.java | 55 ++ .../java/net/jpountz/xxhash/XXHash32JNI.java | 49 ++ .../java/net/jpountz/xxhash/XXHash64.java | 55 ++ .../java/net/jpountz/xxhash/XXHash64JNI.java | 49 ++ .../net/jpountz/xxhash/XXHashConstants.java | 31 + .../net/jpountz/xxhash/XXHashFactory.java | 220 +++++++ .../java/net/jpountz/xxhash/XXHashJNI.java | 43 ++ .../main/java/net/jpountz/xxhash/package.html | 65 +++ .../resources/darwin/x86_64/liblz4-java.dylib | Bin 0 -> 47188 bytes .../main/resources/linux/amd64/liblz4-java.so | Bin 0 -> 59545 bytes .../main/resources/linux/i386/liblz4-java.so | Bin 0 -> 68840 bytes .../main/resources/win32/amd64/liblz4-java.so | Bin 0 -> 161360 bytes forge/.gradle/gradle.log | 88 --- gradle.properties | 2 +- 72 files changed, 7376 insertions(+), 121 deletions(-) create mode 100644 core/build/resources/main/darwin/x86_64/liblz4-java.dylib create mode 100644 core/build/resources/main/linux/amd64/liblz4-java.so create mode 100644 core/build/resources/main/linux/i386/liblz4-java.so create mode 100644 core/build/resources/main/win32/amd64/liblz4-java.so create mode 100644 core/src/main/java/com/boydti/fawe/command/Reload.java create mode 100644 core/src/main/java/com/test.das create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4BlockInputStream.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4BlockOutputStream.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4ByteBufferUtils.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4Compressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4Constants.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4Decompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4Exception.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4Factory.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4FastDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4HCJNICompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4InputStream.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JNI.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JNICompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JNIFastDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JNISafeDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JavaSafeCompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4OutputStream.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4SafeDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4SafeUtils.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4StreamHelper.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4StreamTest.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4UnknownSizeDecompressor.java create mode 100644 core/src/main/java/net/jpountz/lz4/LZ4Utils.java create mode 100644 core/src/main/java/net/jpountz/lz4/package.html create mode 100644 core/src/main/java/net/jpountz/util/ByteBufferUtils.java create mode 100644 core/src/main/java/net/jpountz/util/LZ4UnsafeUtils.java create mode 100644 core/src/main/java/net/jpountz/util/Native.java create mode 100644 core/src/main/java/net/jpountz/util/SafeUtils.java create mode 100644 core/src/main/java/net/jpountz/util/UnsafeUtils.java create mode 100644 core/src/main/java/net/jpountz/util/Utils.java create mode 100644 core/src/main/java/net/jpountz/util/package.html create mode 100644 core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java create mode 100644 core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java create mode 100644 core/src/main/java/net/jpountz/xxhash/StreamingXXHash32.java create mode 100644 core/src/main/java/net/jpountz/xxhash/StreamingXXHash32JNI.java create mode 100644 core/src/main/java/net/jpountz/xxhash/StreamingXXHash64.java create mode 100644 core/src/main/java/net/jpountz/xxhash/StreamingXXHash64JNI.java create mode 100644 core/src/main/java/net/jpountz/xxhash/XXHash32.java create mode 100644 core/src/main/java/net/jpountz/xxhash/XXHash32JNI.java create mode 100644 core/src/main/java/net/jpountz/xxhash/XXHash64.java create mode 100644 core/src/main/java/net/jpountz/xxhash/XXHash64JNI.java create mode 100644 core/src/main/java/net/jpountz/xxhash/XXHashConstants.java create mode 100644 core/src/main/java/net/jpountz/xxhash/XXHashFactory.java create mode 100644 core/src/main/java/net/jpountz/xxhash/XXHashJNI.java create mode 100644 core/src/main/java/net/jpountz/xxhash/package.html create mode 100644 core/src/main/resources/darwin/x86_64/liblz4-java.dylib create mode 100644 core/src/main/resources/linux/amd64/liblz4-java.so create mode 100644 core/src/main/resources/linux/i386/liblz4-java.so create mode 100644 core/src/main/resources/win32/amd64/liblz4-java.so diff --git a/build.gradle b/build.gradle index 0fe483ab..383b2e40 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,5 @@ subprojects { maven {url "http://maven.sk89q.com/artifactory/repo/"} maven {url "http://nexus.theyeticave.net/content/repositories/pub_releases"} maven {url "http://repo.spongepowered.org/maven"} - } } \ No newline at end of file diff --git a/bukkit/build/resources/main/plugin.yml b/bukkit/build/resources/main/plugin.yml index 2cc92946..b11fce19 100644 --- a/bukkit/build/resources/main/plugin.yml +++ b/bukkit/build/resources/main/plugin.yml @@ -18,6 +18,9 @@ commands: stream: description: (FAWE) Stream a schematic into the world aliases: [/stream] + fawe: + description: (FAWE) Reload the plugin + aliases: [/fawe,/fawereload] wrg: description: (FAWE) Select your current WorldEdit Region. aliases: [/wrg,wer,/wer,worldeditregion,/worldeditregion,/region] @@ -30,3 +33,5 @@ permissions: default: false fawe.fixlighting: default: false + fawe.reload: + default: false diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java index 64dd85da..8f07a10d 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java @@ -20,7 +20,7 @@ public class BukkitCommand implements CommandExecutor { @Override public boolean onCommand(final CommandSender sender, final Command cmd, final String label, final String[] args) { final FawePlayer plr = Fawe.imp().wrap(sender); - if (!sender.hasPermission(this.cmd.getPerm()) || sender.isOp()) { + if (!sender.hasPermission(this.cmd.getPerm()) && !sender.isOp()) { BBC.NO_PERM.send(plr, this.cmd.getPerm()); return true; } diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index 2cc92946..b11fce19 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -18,6 +18,9 @@ commands: stream: description: (FAWE) Stream a schematic into the world aliases: [/stream] + fawe: + description: (FAWE) Reload the plugin + aliases: [/fawe,/fawereload] wrg: description: (FAWE) Select your current WorldEdit Region. aliases: [/wrg,wer,/wer,worldeditregion,/worldeditregion,/region] @@ -30,3 +33,5 @@ permissions: default: false fawe.fixlighting: default: false + fawe.reload: + default: false diff --git a/core/build/resources/main/darwin/x86_64/liblz4-java.dylib b/core/build/resources/main/darwin/x86_64/liblz4-java.dylib new file mode 100644 index 0000000000000000000000000000000000000000..9d5cc0e58e3a99ce201dae544636959c8bfdccd6 GIT binary patch literal 47188 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 M8J~N32OwAeZ}C<7HUIzs literal 0 HcmV?d00001 diff --git a/core/build/resources/main/linux/amd64/liblz4-java.so b/core/build/resources/main/linux/amd64/liblz4-java.so new file mode 100644 index 0000000000000000000000000000000000000000..fa143b1eae21cb08fa80f0b1c015b18c0cd62488 GIT binary patch literal 59545 zcmeEv349b))^B$bAYi0g2oP~0I<2N%FcH+WfTpoqs<0ZOfS}?41|m@;G3g|XA3~tB z)zq}+jW{~a#y9HBe9i(h;~?(o07(b}0b~&uB8qeomH-N2&HJBwZ*^BfV3==t@ArPc z*U+iDOWnHn+_T+t&b__Rr`{K5x7*B5yzOoqrLwQ#Ia#70t3ll0l4Z-Z^~B$+Y=c?d z9Xo^M{$;#oCYmbjYF#fj%q#dE3RIrG{~4dY{8 z6M52>fGd4#M0wY%Q`9rBRF^*4djd1*e(7IX@|^2x>JUEW)qL&&JV)cZ*pIcJ$#{r{ zJ!ae_+t6jrpG5K!zN&t!cvjAfDYre4*m08DoPqBwy1{1}zu4U3+Vyg&C1ZfNe;C)> z@Ewcqb@<+a?=XBH#CHn5^zq?42;ax>?Thbde9ccPZl;R_jk%AwACGGWzN$#yBCd3~ z+j2ip+$UM`CgPsPW&QLL2@^h)yaM0*@V%1~_{jXi{$7i}m*abr$jid@5`0tey&c~h z@x2CL`VgEm@$HT81NhQs9KH&^S5iWJa&Vo5?`8NpMcxl_oht6TULHk8e|)>6i&3I- zy5*sVa6i(LHg9jh%@}+okB(xUH-|d%BrJt-hSob*(dIv`NFdI2H&@LcHcLDG4%0Ahi~qe zI`PlFzs?_8G2nN}Lwh`PbjuBe-)X;@J*ndBEj4QzjkkZ-Q1ZdC-)h+ZH_d*Ds$O+W#-#ZA3wRhdsqjy~7F`r3ov-gnDCE~!aha_i@_W_A3r`m6Nt`*-~Ok>Z$SUQ{&sfX>ke#t z%Q@%oH{SKY_Q1ogCl);W(!+b_*uKsW&3tgiA07(I>;4$`RfCc29PQh>f59~$%(?l| zLz%s#ho=u%z2xy#`mLiYez?|1dN%j<_rCt=`~|Z{(tbaS$`-7M){8Gc>YKlC_d9g{?f;IdOyZVpEVFvtp1;Q z`eS~v?PYLCvei(B@iF=6b!@(pY;B^x`QCigp?0#x;E0#d$0yoP|07So#V@vjqW=!j z|9=Iv@GLkb6jhYq<$_XL4nUd z_{Da20?%K*m}dmUd-t$;2OM&F{uLtMFW%cM-us4VXQO~yfq+}8$Ui3Xmx}x*kv~_w zH>;Xwh~{kpF<;r&^L$mzqf@lQ7%qzXh<1J_>Zb|@`KG`Vnzw$CD2)QIM0J}TFCsX! zX*}m`QBcF`C)oQNQ*tJpEICvHe`Uw?F`F zhRA~d1RmN19-92;2{GQ~H}Lw+c%D9g z74y<2@UvEo_lKhW<>|cMO40sXW`Cyhd@(HB8ZllI@J?QAdqL!93;Z+5UrOzUYrAw*5}@zeV)lgy)myxL)TSG2iu_NDtgE-vZ$Z^ zJWreQVz1fGA9%irZr3fHz?B z!%{&&o)h&=$|%oR^hBC@`?hp3McySos&Oz*2Ec)&zZ%` zx-XfQGj9epFk?>Mv#2<>WZHwGg?s1BC@Pv&WE(r9WX80)vjWqeEXXgO8+dlwoM(qm zQy&>VcAPQ|FAiL=0_si6$jN`Ypm0`E(X@;?c~9pBX3ZR594IIb*r+gC*170HFYVN( zd~c^_EbpK8$MQf%(Tv<#tXGyymiJFvF?%u^ke=CT zU_58a6nWTf({c)D%?Qkz5WpYX(=+DG$cq3eeN2 z9lb|>aluU1`mCpO3VtM7pPBc>EP#hs78Q6xl+eVK%qlF(%bz>0`02-I6?Phd?de%h z)61w9d)0&hy7x4%BRa}5a`=#_9`A0n+MN6uGrf5^T`(sne{LXeZZQYB3Bk@-05t2a zPHui-&Me=&K(x)G86~q=0)2ftzogq)oK^s`%bPLhoQ`{eNHh5(v3^@JF?c>?OI{ZY z>D*6CAtB*;3kkQT6&K~reIh!Lu8*F#ls$V69E#_faAc#5C8rh7eQIug>0D016NqQf zJQd+4KbMb=<7Jmwj%9M3kCF3sx|?I5$_Ii`f*RA2nijWLjxnV78?hlc#i| z_AZqyP+;SvCbA1><>t-1WB607bxREf;&W@5{UIb|uNNH0g2L8pK0Hx}&_LW4bs6iq ze5=!F&nfPV&m4RL=z!EY)o+VI;+(Pz6m?;q7pU2}KNqUlE$I=TK2JfHmd{hGYfq!q z&f&51=FOf_G51H*1RSGaXwZ}jMktvM~Dd!gBVyVEiRqy6W@V*Q<# zA$ajc*C!mkxW3;p{9@}}EHof={sm}I?SBOtRR3Rr2GwWKn3MN-&d{R#p?BD(&4L)5 z0fp;vXfH*iZy^hkd5o%zzIWPfLvI^;J6GbMs@dY$KYpd$IGg?dv44Cl)aw~cC*|ql z!Ffo<*@E~>AJhM3>cOek@_fgtRCZ(Lw_L;X(}ccm=9}rZ)x19GWxrShc zYf{;T>i1!H{7<4K{}M~S!;){VrKmVvaVoqo33Crhy;w4gR4y+Ha*sHd(#`KwvOvQ#$Rbj7VaoLn~IE< z4t%bwqcmAVjAoX8m(qm#MlDPKjnYK2#&VW^lhO&4UdqzHr8J?MQNz-&QkqcAC}ZiL zQo0wV3t0Ncl%^>(rnB^7N)t$oY?gkO(nRt`7E2dWnn>M9XXz&?O+zPdOjHUZgno!v&VCg=TCX_d(vveG#FQ;@iOP_ol>A{rF zV(BB4CUc&V&eHoRO(pgz;TB=R1s13w*G&2OL zF+XysM=c(ZL!{BwiJ&AhM;pBf6GVJe-iq?6WM61qxmsoG8!-* z#GjfPKK@>E==R01cy(l_%UX|S>JyQPGz3pfb)v^}oI7Ogl&S@sZK^ik2_S4$wVBQq z6(te0kttVf_UE+wv<`O+JNecw#Hv^Kq}wGIym2%Ws~)IseC!F zr3ai@vbM{ty4qjyjZ{wdN;xOs%+^)sl>0;TN7=mE5!L=}r1cCYt>S=3s}Cfjm5jBj zwhu324YcE3a*)~&3>0O%ebq0eX0{vKF8@$^_k8T6?dsBDy6CVd}lhwoyo_& zp`#`pWQo2T_W)GVkTP@$9S=BV_A*|$+VG>$0)IVT4h?t!W^Gx&y^7$k?eb+D>&Px> z4Kn=rPrzvL2N{|&medyHxJWz9`Azna5v%vMv3&GV# zaIL{K0-pAtFnHei?^bwLcLPs`k|urvH5tan@YAn-%Vu#&m`s zry`D*^-Nlr@C*N#!SDEQ1pGP(A1k|o-~WC3UU%Yu9u*m1v*6)9o#FV;>H8Ut!SnaO z7VwPGx3=9lahw>d_;?;F8#NiV?Q$sTPH$-OU!Qnrpl6b2;-m>uc>%NvIe1o*s(uEP z^4jaw(3k|(9WJ~})oNw!B+ZZBPio5BJnAohtFvz-5fZFT2yc+`j@{b&$qlkQT(nCL zc}|m53bve9LL+Um`$S19#wV@lB?lYqdA0OHdwqDrfV^XW{n4GjmfahRzt-Bq5htuGf~bO;#GKiUi%TBy%Ftr!j1iTGd_$Ettsv`Mdf%J zEi?@YHzoV*J5i^vtTlM;n?3esjQ6tno55dSSR5 zK~&^?ki+#=4%eTaAFf_K-Iuq4gK3==nCk>E8L*;|JVKBpNDC;70s@>q{Bq=*P!mly z5+p4!tiiK13*Ex73BC7BR^C|ylw$Z7O1M+8Zz>yAQV9h2JY?c^V;sZlAyU=dz;HD* zEm5^^P~AHUb5-qA;I;DZcwjHVSXo&GG+`6?P7cyEP9Z1sezMf0NXs_M;ihYFZI&~R z;PyJY9UB_nN7kC;U^r2BHx_M?L)q=J>!cjq-3}V)FNek_AYTeD#DvK1_LAF`mC-)G zW4@8UM_Mt6=!Cyun;hP7yDTj`EV~a z_HD+!T>gdR7LZzs{DE0p4dpwOceeeb_Rj}z*f(7bCEO@$DkNQ~G*N!%N4FfnJ-a3h(04VqHk*y zdw8s#aEB6JdyOhBTPK^jwaC3*2{%&iDv>MO>ySH;CJv8oiROl7JdK=ns(Y$4QdF*L z8~tEuP)OSR(z19Ps-YMQncEUHJl|k(uRa{>G{M&KbU{sHtChkLC$kVEJj zz(4st6D(qPSu)~U>#7i zo#ub6K0F=s3glaMRI#@qnI`KAX*BJ~2KFsGitj4fenhqd*MNO|qPJiMfO8Xpb2SX- zuyE^ybL)U}r#j;tl2)AiSherE5YC+zIJX+F`Xq{Tr;+N6bCmNj$GPns=RP5vTS_>` z^y81xfojI@+RQYq&mvWaIyy3H*^=xdGQJp%{>Ns(8Y ztVg!jqpw`k=M~#`|#S2K`TiaqbE4R8~Sr43Azbu zMCcx@Eua0c*BwN+i@h1Y-hX9`mtirzmJV{>N$3Ue&*9B5yJ*%g4CCp<#Om+!?N_Kro zqx9;-2Z~`vwTrg<^x;=qG=`{laU2B$0bl`K-lY97nu&qvB?7~FAjF{81w>v5tL|WA zZ0Aap$)DyA;Go(UY1~6#Q1q&957A!>M zomMZDz{+J+RK9riF%Erl5|9?!?+~m6^vTIsAF!L0a8qBez0TmvXbU}6(fXj9F=etb zm7&l^eCA&omsEKl6gtT33yjP70qJ~f{1?;j9MtwqmO}{xIhQ@-nW$*rO#+AA0uGx3 z4qGCMsaGiase-3C(vp6rj9<+!WF?gNIUM8bVCo^Ktm~KB~5rgt+S3qeyE>dGy;G z$LZ?rHdAnmY}LM2)zwipA+^^iSyNc1Avk0EF6OWcEvmaaYeY zD}M!;$X-NXdcG5w3V(lbAo_|xq(qe{}zOXWuhmxAqBU1Z`;emA;8jh_q_ z(J)fA*AX9`4xV%21v9s+>NdcSCZD>4Yb{ovxTW=SEfh|ua>RVGUfzK9vdMGXiRXr7 ztd=(vy3s;xsoDs=32Q8B#Igo%q;+bY{BCTW%9uL1_HCBcNvsvJLot4bUNl1A1;}W9 zy`8u?*4MO3#;V%-3N-)dW9M64Th`U4e3JEPu;Wj|s>DQCRi6wwm7m7eXgl%7dq6a9 zEQM8#h~rb_3`a_a+aTODRED&QVu&1GM|k`x2#>y-zSNUYg)GCMf+c&iPL|Rm}Rpq zI)VEIvNq8FZbM-|tW;QP*IB3a(Nbm#}Ht^Xa{?1wN}(>p9Y3vks#kh zoyb`Q&Jt%21=~>X-9=?(Wyg1C)Q0!7su*m2@l1hQ9X6Zu1Q=R0bbnB(sj&MtYr#gWpMYngV+TKXvSHO!JXIzNhjdglcA7yznJ4D}=yI?%3A_zz&Q$Wv!7HP>_Nn$3y3JzlL9cVRNB7xOR~=X9 z!;xeyxe8fiAjd)u>F9hc@j8bn!F^y`2YY55P_-q_G7Lr5-f~vsS~lM~#O8S?3%4Fx zZP}!X2NN(b_(3zh`uolu$ZEaRVn5P0!N&%_sE0aeZ}Nl>_LSXQq{WT&R4BO$_782n z-?hf0-{#Y9fTiP7d$l7tGj&e8;gHSP|Q@^ z>x)v<5fh#1C6~z|@6Zlug(LHU4yk-|4`{JK6-8_Ehg7HC@7m|nw)wQ}aCBe2k`Tmm zd^hj|5_S+ww2h?u&6XA~!DF7_*}hWcQwVGDgdXC^vgjm3lxsPiFWSfP#11qG+ee2I z`gvV#qo~HhZ#>nPQRe-j1a}Q2C#2k(L`9>#MAh9`y5B3U02B!gG8OHx3V-Z8nz}^p zk7^<{EnRgrpOETd^Bz=z#yk1QiG|PtiUtP8S|GJOZg82kS7~_2?Yx-;Xh? zP+Eue4eVvTA}pHcm)>9N37?4fxi+bwrFr*;k}nGtCdl@~s{621PP=x3CjqfD0Du=2 z3Vaot0|T^e;e&9H1ky3(0RpWXoeA!fJY@+^Y0=3uc&g-P))fG?9FKyl>^qCxdRs?* zmL0(fwQ;;!DenuPgOW>8&L@D@NNQ-hz1ktWRZ`q%!d#+1a~58qv#P5$ZyUqzeaTwF z%U->=Coe3p9gBu4xN7|NFu}CqL%bOVbL0RP{Nsl+YqVJNTKTs+~xC^~-$j zWS3O2o)vnXc48DZV+?3i@DGnI*{Nu&ji;H4nFSPoAc1gGrfHIb!x(h)68yvRFdLiw zno)cmp9?VuGS!uWf5$MyEI>~HZdfM36NSA|jTdu#q<*KBTuwYBm7M!ojE5LEux@w> z1;Ai44)EN|kPCVzTD40R1BnAYU?+S8a;#_FA*9SCp5mgGO+1O~=kxGo?W4~*)PrEkC#E50|F-I1FVJylv zk~rJA#^=5^aI4%sQ!2cV7!nu}_>bcLzVt=Eb|~XO>(8hIVA#|Z*l^MfoqzyAmj-AV zPr+Zy*bsF%&6p~ZynZV}Gbq}ENQe=rKQ!(PP1QVbEVMe$1NJ~S4v%{h%zsk=D^%&= zRr=9X%#+?8Mps9!dsyHW6?8POXfV0n=ds+3W73L#nfgmQ3I zoE(Idh@)OOS|LQJGb7$|mSN_LB>ifS`|@HU{RGTc0u>O8FY4!&-X56ib6>{F;!qYx zfW1swakVG&a*wnm?2}fkg-^@1!MGb3h}p*q)tT1%x_AK>8AT2_6TR-f)Jk0fy+t0e zB=&|IbG`1NtPGv3#~ZekIHVPWGq0wxWJxQI!H!KMxeS;AQfd7|My>IBJB`Y)LpIq} zw;`2ZMNtP}+v!rUAKeBccESBC;*f4d00RJGRFDu2u0baN4GBvka5L7M zq?IJfIcu=GgjP~v(Mlkcl9^tDg+Rl?k))HzA^+j}_%=+LsgVq3>l&_))W_3gijsw` z)t6AJbqM+KgRA9E3wJF7{iHgcDv*k=?TUQ51Aqx7+yuo0GqGB>XeF=?E6^&mHB7Mx z9!OGx5nwBn6jCgbm0)X6IW*1=tBRsQ6#$%>)S@Lf5~+Gmlk)oKHYAJz=e;kgQ1th_ zn+33NCBdM0KE6&9q_@oztc&RbTcyS0h@FJ49OKpR@=0&Ud-58H=P2+(xQ^%gv=Lqy z3heOP_xHI|ir-P(9~bpk-StI%J<^J-j!X{(V;)*iv~{qAL(+eZdGKjp`m|k9QU7&J z)Hf5(q)LlF1P*XfPy47z)ME~WpudL0U-7=XN`N38>-;Xl=_U$o!N70#P2zd{> zsY`@urgT3sQ^X{c0R~JSU@7Vk6(m4DK|<$xv|dt>2w&A_KrT!txljxR54b>Lxt5D% z&PA_PhouJglAZv0@Qo*YIv&Cx8C2p8jfNsHCQh}7nH*k&R{;Wn0UoW{6OJT~3wdol z{q9xL;&mV=h~Tn#QvgHrg?u=#-0fE6%I@`$xS=Wb!aV(fv(T>;*AY`l^d~Eu=dA2% z%heJl>z76IfrDefS!}sp4T?&e5-JnIwKUV?b`%d&L;q&4kGC105@DM9fUQ5?1`=cG!`wtL<;2$|hM`e#KxQmxxM$0x2Fm1eW8is4H}?IKbn7nEMEwevO0KCM06 zKE!@9e6p9T4w|WhLL6i9jTmc$;t}C>Iz!^I9jh`q3md%=1wfWb?YnnVk<2(2`RgYg z9g9v8>sp|&vIZ~!yvx=Aodq9X##&znmQb{S(hf%Y9RZurf9XumB4+j{XC|(y{<<@Z z(u-EpJ60i{ieA#?$(6)uR}1-!{$b%kQIWiVZNzP!e3pAL$+I=fmCg z)AJ69!{*Z>(XKvmn0J-BNL{5aQdg;uD$?1Mzi2hR5r)h%8$PVzb6YwCaozfs1-=4! zMqmr6bXm3uA0|3%wvQ6fM5GLPvT86)GX0&)E9kqkT-Kbs8Pq&M#1_Ui~-JKm8*%R$%;2yws~fIDD@_ zra(lb!1|)t*ZV3y3-nRl2URU1OZU}#bv78!qIPtqOsf1F9#gbko<$K17HhA)k`g`? zrySqq)jEQE5h?p*998%cH zdQxiK4W*xZKBOm9?S#j5HuzP%S38c@y`hmG;O*d&9f2pAu!nCz2WB$|WwI}0bFP#> z;LnfSY`Idw4*bdgI#)WmJ9o5{pU|6x7jpLF3y0=OI52Y~xUNHbO|G;nj9Lp5dgJ}j z>rNJ~^V-|JuCqS(*+81T9xapH&S}&~>K^Tc&lUE#+r8R$k96M&bl%;jjns={>bK8dyVE9Xp?499=tF~faxtB z4W4Ke17l1Cv)kl`Frl~SJ?gnJ9%W>(aD_>~!USvUO8(7Sxi(fA1|L~tC~Mq7O@d_+ zBC}{tb2PohXi8PfNT?R)ORb#QE1IS>m*LfvC1mqs@t7+ZQ!(|?5oBxy^U;wEDJ)F8AJ)H8n z)_L3~I6a)Q(!&{oft4O!vvGPz13k#`DnYahMO?zwysDR|<_N^O!zP4V&mv4Lr;0xk zyrrsT$hF`Spb8roVN?v4j_x}@Rb+OeiVch^yhIhxbW0WBJD>^$Wkk-1?upXH`jY2N zyd)y3Wpu%+%kd0lIAzdA7ZY6>uQo>n%3vcphcdd3<~lwaD`gDto-z)!mYc7NQ3iR) zt(5UYJZs7ezMg`JpQNof<%L&g6yfEDPS68Z9cnx?BMTwGghlB<)s3K!9bN>@LQIg| zrIMUO7nBWiS%uEBAq#ZyPvk;-;XjuPh3AzEFHP-22c|swKPMl$&_n4%#6}<+b}05$ zs`d-w+g(svGkDxRXOod@;QLjz*QlcQN5TXQ(KK?AjJd zT}K`KGnYVbP!FM6kgmyTnV9@dy?*Q6ly3L zy(%E&^Lu>t2+iR_uQQucUz@4vl-gsa3Q(imS%!ylr9-tWQNt4XJvQ+=<<6xdquI<@ zE;2Tn8MVlOl@xphik=dhbV>>N5vp5!+#m9P=ZAy$upgRnyFWD1iA_dO?P2043lr2Q znC(aph0QpH89TKvNh0H?vtelWEY|!w;||6dYj7PWEhX#M`i7B>A!tQ`{2}TzNEGTv!OHJmcXiRKt?a= z`MqdT)-#wN6}?N8wcBWss=7CF#e&#bqF4u*)mRM~%ise{_DU;YR)mTst=Og7zs$lC zu=o|#-CVL)b+sLDhSE;+V_~PT=1_fUMUzZ1)U{HOocCoXJ77!{!>;U&90;p)Y|4UC z4AVgI-xT+O!aoC(BA+*aQ$<`mT3;9aVs%<~$|GPxEd4R9^ioRhp%pqtvkg8|MZXEr zz(@>*b`5|b$fLBCSxg}jAM|C zoNX5byo=E=%{*wh9M3|b5D2Ymo8+*O5c~>si72x2{U`%MODa&Qid0_M!eBi*Dpg?} zV6&|MyQOCZtN$oHD=q~4eg;%7o&OQ1=k0Q^Lki?6aP@@d-<6=anv~#4(DN>O53KiO ztf%=?wSLU#k4I=FL)q=2p%}Ke&O3D~% zjFc7aMl!`w05nLM+*feCB1~al{J2o^Rw88;!4YV1(e(_1?Ey5eaIUJ^UX$(9jNU0Y@*$q%I@LsLR3Sm(SA~C237%qd6O8gJNXpglS>v+C*L%OW ze5O+h-0O$q`RDOn9Oi4VZW$ z~ewXzGAEIhUxX*s?Z z6Rrp$p~t@&PcLV5hzhmrf&3m{B9}eUEHb_@Gg^=#m6rfAq;_LvGKF-g+2pvyAMCvL zYu^(qo&r}Mf>+>m3hwJc151TvU7|OoBM1dK8rPgFQa4f!U7KWRIT zH&@gyxQbqusgHw?ag!WBP;qaQ$~)L_@6hf`gepzAV5W!EH75gW$dz}>g z2$(#wBk*k(AXdJIe6pWRDTbCj$2q$&$aw}9XOYVHN%+DOW&~JwvoOp1#)ak}c-e(L zx};mY3I;6e9Lm}xP{Eo+m6>3wW}%Ok`RG&ydVA@9Lt?~LhVEnTAWcljVSTGY_&SIg zMB>l~ayw%+NU94hehG%EeZ3!?SBqdlp|~5Q%6HLuAnjgxBs1rlj}YQCf&nL7=o7iQqY^c2d=j#XykkWMGaYyhoy#4IK9RFX85bht}AJt39lZ zOEk{Vvw*iORW;(4r1oq%cnBDONU9>s86YiH{topqpDIR&C>m^iIV6WBK{%cuAgDOZ zBK$3e-ywOVmsdeY!>DUj_a5TAzTPdq@>Kz^TyZRL4=FIU#BLo51_5^K9f0xHIsEQ$ zX@oXN*}umcLn^-$ucH?rKrBj+7`(JX41Q}?buln$EBI{^RVDv*y$V+cR${7#FabD3 zX(h?INN`_o*@Yn1eGn#GkCW|N!R?G|fRn)!=JMkxwFF9;UUoNMgX~5IEkpIUJ|RYz zRs9^OZ62oJIrL;6);G^#eKQY&&ihFL67fJ{cw#025Ni^ROadm5OFLqCfTf5ZP$2JN zVz9SI0|^%GkDy)bpCHEhZ)1rCf-A`SonVy+WF+P$!%9KCJPXj_)`%Qd4@N##iK#Tx zk;S-c8Jc4^oMQ_+#8Q@<-$OC0(P-$EvYAmUGRBx0#HOY4D-eW4xFb^-ne5ljkUb?E zd>8KjgKQ;`&We!(28_TCGu4|?Qu5piu_24eEv3Z3EcU$t;`W2^tvb?Y=m9~_IJn_2dv43ekk}OkoE}6 zf0pts{?`Zy@KW%(MyPw>a~l|+LqrO#p?p}w`52#r=vj{*LcbHsE5_#Fp@Dwa$k-d# z-xy`rQD_BYZD<3lrpWc^H31!=-6n67?duiSTE^GfX{42P7%7DSy4Op=tMDptXH{#2 zmkUvBB+^2~b`>@Nt$~&=;X~kQA1hjWl&7_Wr%eJ+3oGvJ(u;M(D>6aYb>7~a%Qs2o z!!azz)o#K~#^#K*0&a}2k*R|7HBRR^RDt+fE%;imu^|7^GB#xdJx^6o|1WMs8U_(}g<*WaHOFZ4f?k;6YMlv7*doqVH;&`7He* zh`uH{zAum@fG|2_soLFK_7MeIWFIld2N)m3+Ra|4J&@Rnpk4UAVQxMYdHWw+1*Y~} zmr_I7vKD4aS=rsTz*1(&X#Ed@5iqY12%(bW>lZWw@VQkU2S;ISGz)C>VX%t^Y-aAB zjUvD>%-QG;Tvqvd@9>q^1x67Y&5W{9ECU%EO#sqxE(&uFtW9ljE$Ppm#kij z(GfqERy+gVSd6zk1KwB+-uMhH0#;8)B3`G(vF5``7`{X!f$>HdnNJ!$7>Yvpi z`IU4MIL4SBi_k+L7jdkId?4l=?4LQqxgShG;Hed`3Jvc`<@cf7!U^+x_{5uuV& zccUg+lM@a#tJSj7{2sTM8N>;x<(tin7Gy}}ulB)|h8}`tXWVhpTA^#hwsYTiu+_r8 z^BlA9_(QqOz>`KyFkR3E^iyV^70VS5*cogrSXh3IcCoM=q`K>*DvA#t8%n}L1P0o0 zPsShk!m_h{mMk@{c4obSkygEC7nFlI#f1|eIQ$tnJ$;yzgTJ2K}C|C=!9{06E4@J7iAD|RRw@CA6CSyK5&kXdZL?j=k) zBF97ZRW$e{X%(cnu)xaikxFF{wb%-9m{e>ziZZhOOD<(E6;)=lq6;I>;n4!=YB6in zew&$LBF`Z+qXjjj^2e~=V{01H>Zc%Z?mO~Uz}|hZhXS)bumvbXf!m?6hY?!WgQDz7 zN~Ke0r}J?GkDeoC8SCCNO85XQ6nlV3XMjlLxf~yI4pm3Ybf!Ujyaj&C`w}o>zR($g zTXG#Bpp+C9vA|K>s}XF|RlyO>q;3|x&I+QXUE2`hCRKflY^JFqOdHIbc7Z@iM^sj1>T+V} zxFZBi^x}`huYG9@U?_)qmR7JH0dcS(*@{>%DL4paki2`?Ha#%u@uA@(qsxjv0nf#< zLcuBk%X=91$VamHEK10p!Ue4;^Si&rdgAx7^#m!x(_2%c`lqyl!3*yf3oWT40q>XH zXG*@2Z^G3f6Pu~oP)XHzW*H3?cWkJ%AW7xp#bUy>9pIN; zJJ^b(5zzA6_xjzZrC!fv)KfnE-QvWf%>;{Ia-MJliF0XS7`mdZcC4YYF>bpm)ZGh;ki4UgUnvq(=! z8gQd#xD4Fe)KEfy>|jX_Tou!IJTTOfG(xx^{+jEP@u9#-yO9m3VAh!swM;zCS7hd2 z0L18&8`q&KFzzgt)Y9|AA)JVwrr(1;=G3vsYC*{M)q?Ne%=rE|)QA!>J>oYrE=;<2 znAK_lYI^Q=Gs7g^;buk)YDne3B|;)rPp&y1TyvAa{=s5k*9Y%c$v-!hO#So8KR1!o z18^lWPE3jM|ACORh@!_?C;Jjen`P|(N{|%-AHn`%@*9tY8cG`IcYi8XU5fUBN`hm96kHE#W%R!qH!_Y+ghOuwIrP>>`l1))%`-~yFnbTm38Jk#;jkh@ z_i_N{JPs=|bYnsHWPHrfJv(aX-qMbD!6yj)u0PsV+@F+;BEB`dJ6D3={YmKoBK36T ze2#?K%#rXdjzSYY!PzS8^};8ZW$_8hR-a%Nvu$RR7jHKC1gArGYKQ?~i$3TzSK#x8u zBC0iP3xnP6|Fnm_nQp>r$2G*R_{? zpIF_TZhbG4D*;|nu`5Q|#*+{~y&hE%Qr9mu+p zE{Dcrr+MjjYH0i^HB@@sqHi;UgI`yEC^Jc+0#@F3LzQ7 zVWp{ns?ar?>=m8;Q5O3#vyV&$Q;*355Fv9|(fYw}-$DN1K&wGE6k-(Rz3>XLl27l> zH;glM6`WY+hJALvGes&N3z5d;_nkC>b%=B|Cy+^CD!LIxv01#?OkH3WQ+S=&EDka= z%vl_0X7E`YfLR2DVG~e8eg~URHB`{f92CI_+!sb~g^bi*+T1%4x-OWkOa_h2n7^@$ zHc$33X!ExcjaAW)JHd=a_#B;IuEs)rKLGWe;97CsfzDaFko*XRVD@g2-Uq-s(KT(Xs@HTm-_06y!$!`9I_00`F3`g zFT=B_9gNg+HqWMg%=AYhl$HA<=Mw^$_zZ644~BYK?J6T6Q6m5s!{;}%9QOMA- zBP?uiHAbm~hT}vj@UYWJuzk>NGGgFp+0w$aYt?=dWC06?0__@M>V7Tkw;xi%UxD+J zeQYw>$7J_bW*-By1y3{WW5>~XKLUKq58}pZACvN#@tfPn5}?)9!R+5yx)*C`n8q%J zQ8YOza77ee(h6o7t7cX*=-{OXq5DD494_o*bj%}ZUzV^W`uitFC-;2XbQ+wbM*p zfTHSooft)}F*8hxT5V?3VlJfeXF%6o_~JtF#r)&o9~Z+9mzjEIlwP{>!*ExA2ql^L zVN*B!&_(ojEl-)#%L`VQ?%AFMXMI_ zcsT1@Ewm)z_V;Tzho~0uI*o+~{MZEIss)ijLrPe~HKbac?_ylX76fOcstD;UWWR4Q z2YVFws%ZPv(3F@;NrPp62EXM8WzN6hz5;qQeOE8cqrt#807)5|j9 z1rtFG^N}g7z)|shG@j~Yr?lcBD5%iya03N(A}Od3#nVkZ0t9jP#OiEN1a@wN8)L^I zxVdo)VJuiWM9wv6kbI<7O93399#}7Oz>|Qem_`IYl4)E%uW7svvk0S~)igeul*dtQ zs~UM2`_Lp%KnKjGVdh|jX&gJqxoP})8YSAd#&d)*6y{@vmw?&F8_7OS6|sIs9IR;{ zXK^@+djq)_A-*Z92P!>c&X`IMX@}D*j5I=6@*5b&Xc{)H0X=1R#X#zUx@`SD0v_K; z&Ot>x)+wrk`0o#gg0uliSS-;OSf&`;Ex>Tdt~8vg+%KxkhD9{0n8Mqo+H1K<^Db5a zz7J|Bn^hsyWH&SoQ6<)7#h2p&R$9wS^LwP28Oq$FyjY7sU%1%#4eV#I#Bb;riD}*p%#X%z96+`ueglKT5~_3j27j{|zX1&dySlk; zv+Ngy0m8NkR~-r{g5z?lK6FkTM|tNsj&s8_IGn8d7mw4}1s(uJEEcE1fmHdKSdULB zev`*(yv8L9IkvuhRm5quzAV=H=fr9J6tb_II1SiA9s|e$Z>EUK8kes;KI2YW*bXTJ z>@X+LPgr~gAOnjhDQNIy0u+%McsZ?(nLQNXp;Cg;IT0Ew(a!&5ga&>*f%`IpxZx2R z|1I$u>EPwe>TSknaOB_t8aY2FhRa;I7&G&YkrVPXG%p@M(E7TF&uBw4(e--g_>2uq zulqk1pTQ1|xVZQXvf=)h;xnS*;U}pjAjfnjnP6-;kYO>K{A=+USpcS${~E0?Q%2N( zZ#?x%r}WCytWNu}X+H~A3AAMyM@VKJYk=50#XP~qDKIgZd#fj*RcwxQ;9(s03m$-_ z$Q&m~WpxTWP&oR&SbJx2MSpUCG zFP(s>ChQQ?PDS3MNAw_NK1x5{Y{xEb%XqsRUxe)+|w$MNGeVwi{*<=hNzZZgWJ zV>p=HhlOdBgTs0wb&K;``@wC`ZqPv z3>sV-dmn1Dw?R*%7aDo*V}{K2B*E{$rN3TqCr9L0FY!*(ufRA&&u{M1bG}zD+R?}f zxF%a)it;-Me;QD%|DwJLFylWEnicIo#Y3MU*S7t**p)=vp(f%7iEi#?+N-!6fAD&- z>Vgct z(8ysmG^rhC4n#~p(JuBP0c9cO7#=`=Ge5VgeI{dLOYm#>IS`S{&%+HR4`FWZLuen0 z6V&oeQdJ*_kr@4TzaPm%?cfw#ZF_W2(ngB(#YupH8Dm592g7`|1^XJ1*NbFtn)Ey! z*`(^Ue+ANbFtl~3(C~GCOGASlrE?MAf5iOUeE(4v_8%$s&)EK>-;jIzV)h@cV}9*{ z5bw;dji@s=+)6r5i6X*r#cS5pdHHcwIf1BCZi|P4U(Ux29-o_$)rpreN#K<(m*}pgn?B1UA^PH$vr3 zk*d6SIrCP~9uT~qDPQ=}AOyK88XSGAu#4n#@>VcC1)3Rr5O6VH*=xV&kRq zAJahbXc`c20q(KmLF{?J6pA$&!o^nscC>?O4{hj8qoeM?i7?;f5lP3BBr4H8)+J#d zUPUV)!UM!>?_&Fyc49p`5jA7xawkS+TFO&MUm~X3+JJb7i`cL5&b5Lt9Yl)nnW
zh41} z1Dd+{{R;GxOtc;ii;Z^lD@<^i`~l5)f+rm0&W1x22TDR9P9#lY;Z+FLVhQl@fWs3~ zXxsTjBy3w|IwvY4iXDMuNbNLZ(M^w_%khl{0l_BwwMdu_-m3#XU z@58!x6+3-OD`#hei4!f2MB4F=^%(6J2)6db!5=c-?ZA7mvjYA@^M-gB%SmN77(=G4 z{lv)`E3`_o32VP}zJgNhNmsN6zh04IgRx8ZgEKb3xKmO9KA@*V|MI*;Yew_XKm=~q zC3y6}ONrQeD!U*f&q{COBpdJ#v?Yx>jH8?0dM?Elhf%;3UyD#USlHV=8lC|AhaKr% zf)s_WXw8TZ#)@6h*t>l?q}W;7r1F`^JiO&HkM6C&ndKD88a$hT&6rntM}RSGxD@Q+ z?Pibk_L52LU9M)YcG6Y12<8WrRMkodHJ<34NUFuBRsSVZD=tqn$llI{%dvu7ry7dN60#j9il|D>s*Y)Iix zrC=%E;@8LHE$iiBvnj9m2V|5cYZlw^gYyB?q@YTj(Ot^08|mD?B&RIBe;|A&Ud`F+ z$ItS(K1Frxp*Sp6&S2t``~unrV?i zwLVRKDW!C)pHCM3c!4sWQ$;_ z5|D;J#@p+xU;h=MoIf<)j)T4veOjH*)#P{YP_>;Ug?xVW!P46&^VU%(aD08(pr~O& zz7UYFUUp&UIxODkg!U!0OI&fprv?4Ck+qVHLhd@CUnsG7xq?&B*!irqYngsIvQD+{ zRJ50=dn&Ts@ib|9L=lbMuJ9L9UHN8nsHS}~{5 zQJz_da7`dFoh1eD0`wIf?)YK&QdaR5q~4Gw#4u9|rm*?BL>|@$xQ3%jaZ)L5im`9O z4g@Kdd{V~Mqu9AYgkf0N~Jek>uNZEuGx%J4o2QyqN4xso{D&GsHBCYUrWR9jY zjgWw83vL>m_~qVI`~|TvSi#n*uFq)SyfjXQZaltKmBt?-$6qS@9ogvm1(k6;%uk#x zz!VTY;=2GpY&OX`OPB}#8n{wB5HWHlXxEVG_E4 zfoG!T?u2Umz*jPY?+Nz+7j`f)0*C+)^5Zra?x%?On?O%qy_~()8+z_^F55m42iJHY z#Y-oQCCabJ*rte+S-~Ww#dqLkIBU1B^!!rxGQ>W4f`_ECo|0!#2OZS9fb0LBMTgh{ zt#KXoqv?Rwx;Rn3a1U-MzI8$0`Z&aT9RijDZrGh#O_9f#ad2jKW&?Sn-hl_2(T)3N zr&LbAXrj7FuMA#Ev8vdiRsfG^zE5Y!w*$#6oX^($(k8tPmoJwK1^nK{^2oQ&VF9F84B$7q{>8pUQN zN|h%uSXm$G^y?umCwz$@;=9it1FLQqNyFjja8;Nc~!2ca|`#=}E#qT5e zHP#i6o|w0Z-UJw=pO0+}fY>UH7Mffr3_xFE!~@bsxd``au6OLPA#1#sjRqBrZQC{wx@>{E4pC1Yt1 zBCa?_>=Zar@kS4R4)XK25z~MzITM>1`3d9oJ=+!x%B>OSdD=iawRT>D^E`=m@e7&B znK&=H3q98%ewS~3nBv6|cP$tY(Q;^X97KF}a9@HCKdhRfZ3>?N3t20N)I^Xpg2bf7 zImc)T1d2{&ok27Lo%?7P=RVSj=fH>aocp+NFF*HDdLai7^WaYvWSNcA^{5Llk<%6g zM0ogJ;fe!ZZLN}XB5S z^o5>@C2)4XccJ-$1Fa`a%qHCkA*Ov#Bdi@qq96paBq21h$7xp`6td?Zh1hp(WuY&y zpUXL4C{PEqE1kw@6repXT*c+f@OsjeQ{a&6A5R<|%6xOtO+Z<4pFr0hn?N<7HhZ=8 zwCfAyXwLW+T%W_0V|US%D4&GbX$B``{r!;j7d%lK7ohNU^=@=_8-SY)r2^HqV|$LL94BJiarAd^j@w!ugO!Vgol zx~M=N2y9`}cmQ>!9oX_F%^k*?8H^A$jbg%ah$#048I_$ zzGufRTrSqzt6AVHLs1fV5NH9K;4`p211DhHp`t1NkTc$|9X0M@3@VFFb0T;IL?H`5 z2BKvfZSZEA{v9A)CS2pyl5rZ3z<&dvv6tB5TVKwtA<=2%v&PCW5QY>}U^+5}!f^?F zq7y)=0G%ly%S$?$TBguc8$ZTdYQWH;Y{QNtRsH=4i!7lx%oCc1>N>@Kl;JkQ#!RBh z+1>su1K=ItKg@t%ypmpc2j_V*_V7MKQ@}Pf;V@3Ythcu@Hpx%kAHa`%$Jy_(%!RFp zIi=uq1kbZ=WRcc|WFqojWxEN>M`H$BW^_yx#klk)V+u?|7CuGzRxkKeI;tBZKolw# zrA*uyVMHdVBi?5V%A!j=`VjYEsr(o5&?t~etIDEFu{M)(-F>9;Qp$F}&(^V0Wg4Eu zT53qYXjs@1R$&^uiHF?U?$a)7P z!a8+}6r74G3cR1g2I63T>HW<(K^;1y0v(s^qN{K+2|438VFdnY=N|@(lSllLcfPNF ziMtOSev7(T*-}31ze9Ta5_F%HP#6-9zx|~c!Ux|0d@C-=?88R@h1z%%S%|YJxkAH% zxJO)DgJEx-pzrlJQ{v@w>{CHQw!B9Gf|;f zUZ#dF!!J3~PcOXi7SqDOz6qE6%J%K&=({{_jGeCxgDOxB@{#Mix!NE=4+Py1$K)ea zUW_Al7XJw`>$SaK4>~=8UbPF`HDvX0so#u{Vx0=4EsUb)4${37gLnl&$FP$?St8 z-0KjKw3#o&jlwpvhyvEOfyN-b$>uP!fTxVf;Ye&QHvtXc%m#vjeh^FVLov8JROiv}5aUj71MrlTLbh^efy~2Z|_zfwVV3+CSGl zFz}EnD5Hdq9ls)DbLQ26Bqu>*9(xy{$bKOP{xV|lYwX4h2x`Ix>?D8*32f150)s&q z7sili{4Y=_s26>&k854Z=okl6w+2mlvN9D5cFTFu#E#pn{>ffZ|D;C_Erihc9CZs4@ zGleW7N)9I<(-D|Bs#q$==rA!K+R8|Kgg%OOf_8k-jr1Twset2%uWQAT$S8*q13#$| z+#T;jOK9c3P!gH*XuEA-6kHCMW|Ofa6nHTOyDM-?1s);CMt;IT_)c=AvlL#0U#&*K zs@EMZ{MKu4^tv|s+)aV2>ELI9E=@ZaJLs9l=XWZh|88WTrjJc(vPS& zI6rq5Gwe-uV&HR}JAgyvaBiXVz#}-GhZtfT+0)3=4pxanB5`c<#>kIL0X;g_ZB@Vp zo2+?QIZjH>sKt@CEX?&_WVnG^I5JvW9a%e%IAseFG(t`0P?QM_&EiKpK#y!Gimu7< zpxZLm8gy1wY&@W+i|F4JvWsJi%uenpF*`|~ik=RM z^%P4C>|~*yqJML+aUJ{@TneiL^mIz>ydVtPSc#XJ@b8Wv0QNW!zOz4{*g|o>;0WAv zPiz6|VhRn#5qXhDa1M8P4!O)XE{A+}Fiy}oV-w69&bS0;7zZ(4b7sId;NO1@znn-@ zkjfn6%%P8wAbfy*qVW)2$3y(AYyV}$Be4D#b=Uti>Ob3enG)Gh+}$8nvRt2?c3-B@ zXi{;LMkBEf1FR2z{HGjZ&S(s~@au^*hIASOGgf6Wys^YnAXvdjMEmA^P(TL{br9f) zyh@G4;x(vT3uD-3`LpMiXRH;Sh&&c66!eDY&B83RkQ_PU!&xJKQ}OmX*d98v(`Btk z$`z@wyy4U-(&sJs4daI1Karb@grqBYT8ko4!Q>T!1xdg!)$$o+n`5S*9f=M8AdQZ zM!YdJz=b}``t45)Mu)XszKjDM*#)idSmRfy|1E^yKoZt}unZoYgBAyoH$BJjY4{PK z^`4HOrqenY7S0=$9E38A1g7lku0QBSn(=iJO(Nco0H0mx%C8exS3bWy6;Ld&%EH({ z=s}LqQGAZ!FKue4pJTv;;dHr3f3!~s)&NEp83>pWBzF}Lq0=GtE1@6Gff5MvC#H#* zArqe>m*6@25fKXc4-H7!!_r{++R$l!m9e4W z=}HbZDIbdL#Q7EsDq?EcWHKH|(4VES35`99p^T!%5eA&X za6;Czu)eh7MMn$s`(P8wZ1li{2SP%O_Ghf=7{9(qeDTFdFNV((7?5K4yn!uhZcjp2{&V=u zcJ^8j`dYkzu}p^1pX8eueO31uE%b}XQ+Oh5vjDqqtS}>^W=<@8Mn@vG53jE#}^02=lW+oonQDPUtwW> zp$!?S`MIf2vn=x&Qa8GM%8-tJ!9Uo?tOo?`ls3!Ft0;yV%Rgqi>+>LH^HYnP=+}Nl z{D{?f$z)_M$B*E(;C?yAn}`RRaZSZFl{gHp1z>drxGo2Gd<9n?xwBg#kxT7N*5~iTMqhmI% zj_Qt%mAE$R=ohZbmvnTbgNdd79Ph_99oOZ!PRF$oSKF&-2iIC$Qy^NJaU~JchAUZo zZNEUfl#lBxxVHQn%0J~}|5YZ;=55%mRgG&IwyQ12HT@4*SM)|XcClyU+J-X{mg1Va z4E@429oJ@Dvv7@2`CDiQI=katw1aCwJ;q1rO&uL+@bc7b13YkDifaw7%W?e!uC=(< z;_BFrapF21R~x)~HMpkYx)j$;T(frouDCirMfTtA z@h~QH5;bsDSy5eeGZsnIzh#?=6~hjkK2qY7MTa68l5%8k9ke(V>{PbZG)T4~MU=s5 z-7IZ5AWeg{K-I=cjlgKtBIu04NYynfyw<~%EzQ!d8~2@i&mrG?d{P8#*agIM-*?YB z_uO;OJ@>x%DCr9HQ~OV$A749&{yX%8#z5yl7eR}l9q)jT+Ci5=L+?Vb2|3Vy&?0CI zH2NRV3mOBR2hD=M3>tn9@}MoC%b-i3A$(ha9%D9vhJI5jb%3^j9srGkW>31hTZ4%uuh{ICy>iLv_=}T{r&G9d~ZM zg+xid8$a_{1HVT=w;4KM)+zZzxI7Phh!)l0i48OBKVx>B37!qit~*^DxU|u%>#nDM z9>LFIdL_Oefv->TJrBP3kdJpd_06t38#rBiCK&h*_y!c;8{i8e9k13e*-gJL*^V_z zp)6%5Dh<%z0(_Ci-DQT(1cuGV)4?t?JX_moHqET-G+Xd*Ax~{In>x+#Ub7KA17=+p zLQwgFzBsf{D3$KR+9Ub1f&0wHGbDeyw##gqCHa~4oo4%q4S|2$XtqEn+Q0+eF0*bI z=^^``f}YFAN+m3Xav_|a87>0H)GB}Pg1-sB1pJ~8|662_5C89gzYM&U^l%|P4H)+l z@F4*~X)d<`Z#<6JrW~gH(nWshA;0vHU%JUJeP&0ntp>x2DEELx4{M-4*yscI(rEQ~ zXQ6-Uvxs@vC53D_NArMOr*fzACqj|^iGF9PU-oC884b3em-dLb)zLrcqSGc*$zG8<0>gSSI4)M?f|0F$WSEoi^`xl-v(>6*YxzT@buA6dB@6$fvipXtX+B}(m5XkM>T z|G;k*5S08;3w!2({}thbCu(Pcd(Fk^n%O`gfi)FT6GVL5z*qOhQi=WzB>2|N)DD|V z;0vA(1ajc)2PgG248D2rZ6ZD{TR>C5{{ncJF!=pM;4_F^T}W=0+nsT}4fr|WbC7$T zSe0C#84mn8Cac%1>#RR06>6q=djtF}k7IpcyuS11jF(vOx2sH>(zg}sc=?G^>BhC_ z!$u!iWq!!M0`x8XCH4f0a~02?I8gRQC_nU>v0!mE`?f;G1?Y=mZ$5>(o|mZEqGlys z_Am8&4SW~D_gr}#JrsXfznqtb%Ij#*oC^f!SLvAa?L>ezJzXkYD(k~K;&|Rg>$0b; zZ!hV4*xYIuX%Hu3RU-7w8 z>87$izj3Plq0gKT1RoBr98hH61?Zc`S?UlO?iWuL`v%Fr3;up9+t+|aIrptn>2cb4 zR@BGyfO%alK*_!Y^ew%Lzt~rlm$ichsaD*yKAb%Xi(g8%%hbwm0ZFz<^${J&X;YW|@w`(vEhKLq6} zo>bmZ=b?&y9Pdjtf$o*VUC%$+_fOUHc?tMM;3@JK&u8HNia+W5k&Sq^7^il%@9&{~ zzZ;tg0ul)Vdq2%v0|G4uK4*T|27DXv1Hcz3Z!7sulE-EgJP(&P0)htRMt%MT|5ZFL zJ`Hw#{@qO{-@DBy9fAW1vW)tB1bk6EJ;yRG7d8LD`++}6s5(Ehd`!u!)hM*{c)@S8{u-rf%Z!vB={z4+t67r{@jUWk7m^FI&#GVl*G9%cLj@Fn2R`;Tql zxdQwu@E3{S=|6Q75;%ed$EQzy4%&o!ys2N7N{?xNoNw;d`{-xY;pRaaC(TP6_?E%< zpytEs-Gw7?;6ZQ=!nLYU|A)ZW|10c|gjDx$gFjNw>zMp83;wI%clw?1bHJB8IO+d3 z@XNs0I=*Y*AHVkg<7=RMRq$2DH$>x;BOZE2EZ}D+_}c$v?c+;<|1$W!<9h`7q6b%< z;3IsCt}Wdxh^Djzsl<`{sBF^2X#ag(!BADXSk&~Rd|mfSSk+X_p(eK6$n7C+r+B9# z?X~*SP`@IJ`mg`Hlp%GvgYV{2^FLsRnD@#5+^Fg(L$-4O5>|xMd=7c zIbcX2@Oux+ZC8n=2{Rz`wFnyQl`mpZd2Bx<$-NCe%>3*hTOph|1 zVfqBqFEf3K>1#}X!t@tR-(|X<2O=~rhK&ieWV8k4+TfBcO$xrsGmeX9{-n;q+wl$Ye5=_;@m5sH^dKe%xqI97{t|(?U+S z9JO=#WG3ypu+WyXQ*jdDS~gWMnv>}yJ`45~KF4r@woD>kh#SrJxHXoGPuSLY04y{S@ohdj^y)3^Jr#b!cG^A|9zolu>n7_4(XW~ z6``~bIE&;5!RVO^OMdT%q2z{M7#eU%vdN0~3N6EG1 zDWPGOC;O>yv7ce}yoGd&yyQip3(P3}i@c0KjdrKJ#Iv{~|yCx0+EXy?b#MZ~yaXbjpvbLlVwEOrP>hfXtuh zd=3?2k^N524e}z(%kRCWKU{phLtf4mp;rK+c=^58-vbZXFa0;Z62=wI2Wyo7K6oki zL|*pIFv~BJauhkYNdGbqi)eH9zkDG~?8fp1MT{m*%AX=Hl>V@~Q(n$Bjjw8v5ce+% zL{8|N9(md4nqJcq!snHj>$gxL9vOcN?~^SoFY8?Rz54zc4P;`AFfbK)IUh>13`Zy( z1Ep`_Av(nU|CDH*Ok||J?ZXhU7Ij9xCT6H~v9G@{t?g zY)GDQ;~zra2sif;^8JaM|09O1PdDCZ$hvdmayM2%b-Y*m zlh#qK@ph@gscRJXHAeNgJ_bC1dCA`FmiS}F=RNq{jC=1d4+F2k-ICZ(_E9;CpBkgu z&tC#g{mT6+@lyFJeroXY)jMAof%}c?XNtekXy<$->zeX~AOC+Ue$xo?{wVt(In0lL z9R{NOgD>b(QSY`uZ(}^#t6)RDqe8V0xSt;S6aS>A;|rQ!J=;TdYz6+Gp^g06;ThLT z^ZPmIsiD&h?^n7P!}y}YtLOb`g;&Sn^9p~vIPB%YG&J-vi%_@y2*V z7~+PXGoF1`198Te84vx92Gp}DROwg5H#3iu(4S@{{n3*eUC)hoF~0PI1}-s92V>%& z8`FS#mw+n0LnVBX0uP1FGF}26R*3uK$D;ph8kcwrVDnIV1~q;wi#0GFV|yNCyo>RU z^BUN|_}z?08Gn-T4Db-vwb%bgnSa@1&pF16MXf;Wq<5WU=Q0m;0e+|QE5?^Tp|3Y; zV*D55b994&a!M0pBNonPpS&GXIG!$jT{H6W(P(G9NC#aM_3Rz=KEWSWuzJ@=wLf8e zjspjgt;(Z<(}5F(AIBJf&G<4K_)Xnxd|&W$8jv{oso)%Mbbh9C-8$``1&#;t8@&TJ)=SOP7hW3UXq$Go)_^0{gG_H zb#Y|{e3|tWAJF>MyDwBtIE3`i-|z1C7RHOWYg~P|1Jx$rVGX%HKgImhJTKz6nCRg= zpuP(M$qB}@ZCcGd$07ZCH`x>8c}FCv@+jl;jK9wKlZ-FLG@#x&qWU7^Azl}knEx%t zr|-~!`pyZezhu0H<3ZwfGZrS<6XJMyhV?WvKF|Kar7HI`J{Qy1+-%T0OuzX$$^1o6 zJU_#DhbMl%$N1twtzW%MM)ghLlpp$E3+o2{{5kV43~B(wROQ!+1#N9V(w^ zJk0Uas$t_v#1059g zP8@Vr_=^fI-+%obt-gDL>L(a) zIi+!l|GR*PHRSp{$oxf~kkj0nWLypiDaNM-|EdN=|6_vh(zyDr5UNiD=a6L$ntV&~ z``k~B##1Tl@I)qUo#mB?6!Q<;%?%1RV6xx5vhoHFnrIGeHycA|Ml%conR*@wxZN7DKMvxSGo zxnqWYB%PR?m^cOz$CX7-vR$lcyI>v3W+u~x<5uc;n>BP_+u)u745I4m%u`eJFdZ!z zfdN+3z-*C)IXj=X4%=xvmmI~j>vX=5&1JI9t?$RMo%D=Dgc$oHRk{qPb-I7+z=3|4 zp>4FR-g`QC_gFo9y5VWOQ!qbN4_59K*XK!reEtx2to=+S}W^ zuV>gA?(7=su?%S6(Pm*hc0n7Tv*Y-$O%Y=d-}AOsGe@}=p119ej=rIRu87sz+}iv} zr6r#{ZfC~!6`*WlUjYi&piWz|eKL#J0=CkUu*<$4i{}gQR=SW(PwGZ#>W{QqspLcw zA%hvrP8O_(l7(^ADMn!p4DYro5eaWlJPyZ4;HCE;Axyfn)!Lp&9=7xB9-jOz1W;ma z8sOgT>V1kccwn0Q(lZFdveny>HQPw@uy%VKBg&_e340(7$8}*b+c{XKhdrtyLC+r9 znvWl~yOW~@LoL6x_T`<(e0nXo&1&8z^U3t#@{~AQX?g0fLCUG@5y(;QC|_w> z%0b7gUmNX+Qk9?_^PkGZ6Mlv$Gs}5m&F-#ryQ6Jwk$tB-*Hac+Yb20Mw#%&QM54UJ z>y2_|H!@$sHYR-L%I(lrclcML#hOe%kj^}m);Vz>^0Ar<$DAowWwDbd<0+&Yq%%^@ zZ;AIzPK?+&&+K_}RHbW77qVPc!mtZ^#&|TO-qXMCKn>i}mpf8;o8o}b(V_ROsws9X zIqI=!^Umo!4bBtg+xzJ^|uhkq}SY#8Kqn^$3ea&}vr&bx+ zD#23;Id)KGnY-A^3xZH@!dC-utgjXJYp)-~20x$qWwjk`$eq^UQR^N)9Y#{|d>%(b zasFztqPG7^_Nnrke}~p~@`r1JaOQG_K_!Nj!yHS{C1zFaM>71?)X6?WX;&RY!_n!X&!1qeH literal 0 HcmV?d00001 diff --git a/core/build/resources/main/linux/i386/liblz4-java.so b/core/build/resources/main/linux/i386/liblz4-java.so new file mode 100644 index 0000000000000000000000000000000000000000..aa50fd15b9283391987011af38671d7e6e8c83d2 GIT binary patch literal 68840 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 Z3v>nbEX%RRt*w@EJkF^_Tp-k({|oERv|s=L literal 0 HcmV?d00001 diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index e72fa648..ac2a4e4e 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -1,6 +1,7 @@ package com.boydti.fawe; import com.boydti.fawe.command.FixLighting; +import com.boydti.fawe.command.Reload; import com.boydti.fawe.command.Stream; import com.boydti.fawe.command.Wea; import com.boydti.fawe.command.WorldEditRegion; @@ -43,6 +44,7 @@ import javax.management.InstanceAlreadyExistsException; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; +import net.jpountz.util.Native; /**[ WorldEdit action] * | @@ -195,9 +197,10 @@ public class Fawe { this.IMP.setupCommand("fixlighting", new FixLighting()); this.IMP.setupCommand("stream", new Stream()); this.IMP.setupCommand("wrg", new WorldEditRegion()); + this.IMP.setupCommand("fawe", new Reload()); } - private void setupConfigs() { + public void setupConfigs() { // Setting up config.yml Settings.setup(new File(this.IMP.getDirectory(), "config.yml")); // Setting up message.yml @@ -233,6 +236,11 @@ public class Fawe { e.printStackTrace(); IMP.debug("Incompatible version of WorldEdit, please update the plugin or contact the Author!"); } + try { + Native.load(); + } catch (Throwable e) { + e.printStackTrace(); + } } private void setupMemoryListener() { diff --git a/core/src/main/java/com/boydti/fawe/command/Reload.java b/core/src/main/java/com/boydti/fawe/command/Reload.java new file mode 100644 index 00000000..97014131 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/command/Reload.java @@ -0,0 +1,19 @@ +package com.boydti.fawe.command; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; + +public class Reload extends FaweCommand { + + public Reload() { + super("fawe.reload"); + } + + @Override + public boolean execute(final FawePlayer player, final String... args) { + Fawe.get().setupConfigs(); + player.sendMessage("&d[FAWE] Reloaded configuration"); + return true; + } +} diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index cecf0f1d..0041ab76 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -25,7 +25,8 @@ public class Settings { public static long MEM_FREE = 95; public static boolean ENABLE_HARD_LIMIT = true; public static boolean STORE_HISTORY_ON_DISK = false; - public static boolean COMPRESS_HISTORY = false; + public static int COMPRESSION_LEVEL = 0; + public static int BUFFER_SIZE = 59049; public static boolean METRICS = true; public static void setup(final File file) { @@ -51,7 +52,7 @@ public class Settings { options.put("crash-mitigation", ENABLE_HARD_LIMIT); options.put("fix-all-lighting", FIX_ALL_LIGHTING); options.put("history.use-disk", STORE_HISTORY_ON_DISK); - options.put("history.compress", COMPRESS_HISTORY); + options.put("history.compress", false); options.put("metrics", METRICS); for (final Entry node : options.entrySet()) { @@ -70,7 +71,8 @@ public class Settings { WE_BLACKLIST = config.getStringList("command-blacklist"); ENABLE_HARD_LIMIT = config.getBoolean("crash-mitigation"); METRICS = config.getBoolean("metrics"); - COMPRESS_HISTORY = config.getBoolean("history.compress"); + COMPRESSION_LEVEL = config.getInt("history.compression-level", config.getBoolean("history.compress") ? 1 : 0); + BUFFER_SIZE = config.getInt("history.buffer-size", 59049); if (STORE_HISTORY_ON_DISK = config.getBoolean("history.use-disk")) { LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; } diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java index 7cdbddca..cae52100 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java @@ -31,6 +31,10 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4InputStream; +import net.jpountz.lz4.LZ4OutputStream; /** * Store the change on disk @@ -232,7 +236,14 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { bdFile.getParentFile().mkdirs(); bdFile.createNewFile(); FileOutputStream stream = new FileOutputStream(bdFile); - osBD = Settings.COMPRESS_HISTORY ? new GZIPOutputStream(stream, true) : stream; + LZ4Factory factory = LZ4Factory.fastestInstance(); + LZ4Compressor compressor = factory.fastCompressor(); + osBD = new LZ4OutputStream(stream, Settings.BUFFER_SIZE, factory.fastCompressor()); + if (Settings.COMPRESSION_LEVEL > 0) { +// Deflater deflater = new Deflater(Math.min(9, Settings.COMPRESSION_LEVEL), true); +// osBD = new DeflaterOutputStream(osBD, deflater, true); + osBD = new LZ4OutputStream(osBD, Settings.BUFFER_SIZE, factory.highCompressor()); + } ox = x; oz = z; osBD.write((byte) (ox >> 24)); @@ -307,7 +318,14 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { final NBTInputStream nbtt = osNBTT != null ? new NBTInputStream(new GZIPInputStream(new FileInputStream(nbttFile))) : null; FileInputStream fis = new FileInputStream(bdFile); - final InputStream gis = Settings.COMPRESS_HISTORY ? new GZIPInputStream(fis) : fis; + LZ4Factory factory = LZ4Factory.fastestInstance(); + LZ4Compressor compressor = factory.fastCompressor(); + final InputStream gis; + if (Settings.COMPRESSION_LEVEL > 0) { + gis = new LZ4InputStream(new LZ4InputStream(fis)); + } else { + gis = new LZ4InputStream(fis); + } gis.skip(8); return new Iterator() { diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java index db6676f0..ccde4d33 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java @@ -1,19 +1,6 @@ package com.boydti.fawe.object.changeset; -import com.sk89q.worldedit.Vector; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - +import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.Settings; import com.boydti.fawe.util.MainUtil; @@ -23,10 +10,23 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.IntTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.history.change.BlockChange; import com.sk89q.worldedit.history.change.Change; import com.sk89q.worldedit.history.changeset.ChangeSet; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4InputStream; +import net.jpountz.lz4.LZ4OutputStream; /** * ChangeSet optimized for low memory usage @@ -40,23 +40,27 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { private ArrayDeque toTags; private byte[] ids; + private Object lock; + private int decompressedLength; + private ByteArrayOutputStream idsStream; private OutputStream idsStreamZip; - + private ArrayDeque entities; - + int ox; int oz; - private final AtomicInteger size; + private int size; public MemoryOptimizedHistory() { - size = new AtomicInteger(); + } @Override public void add(int x, int y, int z, int combinedFrom, int combinedTo) { + size++; try { OutputStream stream = getBAOS(x, y, z); //x @@ -124,7 +128,6 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { @Override public void add(Change arg) { - size.incrementAndGet(); if ((arg instanceof BlockChange)) { BlockChange change = (BlockChange) arg; BlockVector loc = change.getPosition(); @@ -143,8 +146,14 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { if (idsStreamZip != null) { return idsStreamZip; } - idsStream = new ByteArrayOutputStream(9216); - idsStreamZip = Settings.COMPRESS_HISTORY ? new GZIPOutputStream(idsStream, true) : idsStream; + LZ4Factory factory = LZ4Factory.fastestInstance(); + idsStream = new ByteArrayOutputStream(Settings.BUFFER_SIZE); + idsStreamZip = new LZ4OutputStream(idsStream, Settings.BUFFER_SIZE, factory.fastCompressor()); + if (Settings.COMPRESSION_LEVEL > 0) { +// Deflater deflater = new Deflater(Math.min(9, Settings.COMPRESSION_LEVEL), true); +// idsStreamZip = new DeflaterOutputStream(idsStreamZip, deflater, true); + idsStreamZip = new LZ4OutputStream(idsStreamZip, Settings.BUFFER_SIZE, factory.highCompressor()); + } ox = x; oz = z; return idsStreamZip; @@ -153,6 +162,13 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { @SuppressWarnings("resource") public Iterator getIterator(final boolean dir) { flush(); + if (lock != null) { + try { + lock.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } try { Iterator idsIterator; Iterator entsIterator = entities != null ? entities.iterator() : new ArrayList().iterator(); @@ -160,9 +176,13 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { idsIterator = new ArrayList().iterator(); } else { ByteArrayInputStream bais = new ByteArrayInputStream(ids); - final InputStream gis = Settings.COMPRESS_HISTORY ? new GZIPInputStream(bais) : bais; + final InputStream gis; + if (Settings.COMPRESSION_LEVEL > 0) { + gis = new LZ4InputStream(new LZ4InputStream(bais)); + } else { + gis = new LZ4InputStream(bais); + } idsIterator = new Iterator() { - private final Iterator lastFromIter = fromTags != null ? fromTags.iterator() : null; private final Iterator lastToIter = toTags != null ? toTags.iterator() : null; private CompoundTag lastFrom = read(lastFromIter); @@ -253,7 +273,7 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { @Override public int size() { - return size.get(); + return size; } @Override @@ -264,6 +284,14 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { idsStreamZip.flush(); idsStreamZip.close(); ids = idsStream.toByteArray(); + // Estimate + int total = 0x18 * size; + int ratio = total / ids.length; + int saved = total - ids.length; + if (ratio > 3) { + // TODO remove this debug message + Fawe.debug("[FAWE] History compressed. Saved ~ " + saved + "b (" + ratio + "x smaller)"); + } idsStream = null; idsStreamZip = null; } catch (IOException e) { diff --git a/core/src/main/java/com/test.das b/core/src/main/java/com/test.das new file mode 100644 index 00000000..e69de29b diff --git a/core/src/main/java/net/jpountz/lz4/LZ4BlockInputStream.java b/core/src/main/java/net/jpountz/lz4/LZ4BlockInputStream.java new file mode 100644 index 00000000..95ca3f42 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4BlockInputStream.java @@ -0,0 +1,248 @@ +package 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 net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_LEVEL_BASE; +import static net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_METHOD_LZ4; +import static net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_METHOD_RAW; +import static net.jpountz.lz4.LZ4BlockOutputStream.DEFAULT_SEED; +import static net.jpountz.lz4.LZ4BlockOutputStream.HEADER_LENGTH; +import static net.jpountz.lz4.LZ4BlockOutputStream.MAGIC; +import static 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 net.jpountz.util.SafeUtils; +import net.jpountz.util.Utils; +import net.jpountz.xxhash.StreamingXXHash32; +import net.jpountz.xxhash.XXHash32; +import 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 byte[] buffer; + private byte[] compressedBuffer; + private int originalLen; + private int o; + private boolean finished; + + /** + * Create a new {@link 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 + */ + public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor, Checksum checksum) { + super(in); + this.decompressor = decompressor; + this.checksum = checksum; + this.buffer = new byte[0]; + this.compressedBuffer = new byte[HEADER_LENGTH]; + o = originalLen = 0; + finished = false; + } + + /** + * Create a new instance using {@link XXHash32} for checksuming. + * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor, Checksum) + * @see StreamingXXHash32#asChecksum() + */ + public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor) { + this(in, decompressor, XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum()); + } + + /** + * Create a new instance which uses the fastest {@link LZ4FastDecompressor} available. + * @see LZ4Factory#fastestInstance() + * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor) + */ + 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 (finished) { + return -1; + } + if (o == originalLen) { + refill(); + } + if (finished) { + return -1; + } + final int skipped = (int) Math.min(n, originalLen - o); + o += skipped; + return skipped; + } + + private void refill() throws IOException { + readFully(compressedBuffer, HEADER_LENGTH); + 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"); + } + 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 < originalLen) { + 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/core/src/main/java/net/jpountz/lz4/LZ4BlockOutputStream.java b/core/src/main/java/net/jpountz/lz4/LZ4BlockOutputStream.java new file mode 100644 index 00000000..86e172b0 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4BlockOutputStream.java @@ -0,0 +1,259 @@ +package 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 net.jpountz.util.SafeUtils; +import net.jpountz.xxhash.StreamingXXHash32; +import net.jpountz.xxhash.XXHashFactory; + +/** + * Streaming LZ4. + *

+ * This class compresses data into fixed-size blocks of compressed data. + * @see LZ4BlockInputStream + */ +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; + + /** + * Create 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); + } + + /** + * Create a new instance which checks stream integrity using + * {@link StreamingXXHash32} and doesn't sync flush. + * @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); + } + + /** + * Create a new instance which compresses with the standard LZ4 compression + * algorithm. + * @see #LZ4BlockOutputStream(OutputStream, int, LZ4Compressor) + * @see LZ4Factory#fastCompressor() + */ + public LZ4BlockOutputStream(OutputStream out, int blockSize) { + this(out, blockSize, LZ4Factory.fastestInstance().fastCompressor()); + } + + /** + * Create a new instance which compresses into blocks of 64 KB. + * @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; + } + + /** + * Flush 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. + */ + 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/core/src/main/java/net/jpountz/lz4/LZ4ByteBufferUtils.java b/core/src/main/java/net/jpountz/lz4/LZ4ByteBufferUtils.java new file mode 100644 index 00000000..3c0b9ac6 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4ByteBufferUtils.java @@ -0,0 +1,237 @@ +package 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 net.jpountz.lz4.LZ4Constants.COPY_LENGTH; +import static net.jpountz.lz4.LZ4Constants.LAST_LITERALS; +import static net.jpountz.lz4.LZ4Constants.ML_BITS; +import static net.jpountz.lz4.LZ4Constants.ML_MASK; +import static net.jpountz.lz4.LZ4Constants.RUN_MASK; +import static net.jpountz.util.ByteBufferUtils.readByte; +import static net.jpountz.util.ByteBufferUtils.readInt; +import static net.jpountz.util.ByteBufferUtils.readLong; +import static net.jpountz.util.ByteBufferUtils.writeByte; +import static net.jpountz.util.ByteBufferUtils.writeInt; +import static 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/core/src/main/java/net/jpountz/lz4/LZ4Compressor.java b/core/src/main/java/net/jpountz/lz4/LZ4Compressor.java new file mode 100644 index 00000000..7feb5fde --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4Compressor.java @@ -0,0 +1,126 @@ +package 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 { + + /** Return the maximum compressed length for an input of size length. */ + @SuppressWarnings("static-method") + public final int maxCompressedLength(int length) { + return LZ4Utils.maxCompressedLength(length); + } + + /** + * Compress src[srcOff:srcOff+srcLen] into + * dest[destOff:destOff+destLen] and return 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). + * + * @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); + + /** + * Compress src[srcOff:srcOff+srcLen] into + * dest[destOff:destOff+destLen] and return 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. + * + * @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)}. + */ + 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)}. + */ + 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);
+   * 
+ */ + 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)}. + */ + public final byte[] compress(byte[] src) { + return compress(src, 0, src.length); + } + + /** + * Compress src into dest. Calling this method + * will update the positions of both {@link ByteBuffer}s. + */ + 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/core/src/main/java/net/jpountz/lz4/LZ4Constants.java b/core/src/main/java/net/jpountz/lz4/LZ4Constants.java new file mode 100644 index 00000000..710007ec --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4Constants.java @@ -0,0 +1,53 @@ +package 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/core/src/main/java/net/jpountz/lz4/LZ4Decompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4Decompressor.java new file mode 100644 index 00000000..6b2c1833 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4Decompressor.java @@ -0,0 +1,25 @@ +package 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/core/src/main/java/net/jpountz/lz4/LZ4Exception.java b/core/src/main/java/net/jpountz/lz4/LZ4Exception.java new file mode 100644 index 00000000..cb45c0aa --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4Exception.java @@ -0,0 +1,36 @@ +package 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/core/src/main/java/net/jpountz/lz4/LZ4Factory.java b/core/src/main/java/net/jpountz/lz4/LZ4Factory.java new file mode 100644 index 00000000..7d1cf271 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4Factory.java @@ -0,0 +1,258 @@ +package 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 net.jpountz.util.Native; +import net.jpountz.util.Utils; +import static net.jpountz.lz4.LZ4Constants.DEFAULT_COMPRESSION_LEVEL; +import static net.jpountz.lz4.LZ4Constants.MAX_COMPRESSION_LEVEL; + +/** + * Entry point for the LZ4 API. + *

+ * 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 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; + + /** + * Return 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. + *
+ */ + public static synchronized LZ4Factory nativeInstance() { + if (NATIVE_INSTANCE == null) { + NATIVE_INSTANCE = instance("JNI"); + } + return NATIVE_INSTANCE; + } + + /** 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; + } + + /** 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; + } + + /** + * Return 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}. + */ + public static LZ4Factory fastestJavaInstance() { + if (Utils.isUnalignedAccessAllowed()) { + try { + return unsafeInstance(); + } catch (Throwable t) { + return safeInstance(); + } + } else { + return safeInstance(); + } + } + + /** + * Return 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. + */ + 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[MAX_COMPRESSION_LEVEL+1]; + + private LZ4Factory(String impl) throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException { + this.impl = impl; + fastCompressor = classInstance("net.jpountz.lz4.LZ4" + impl + "Compressor"); + highCompressor = classInstance("net.jpountz.lz4.LZ4HC" + impl + "Compressor"); + fastDecompressor = classInstance("net.jpountz.lz4.LZ4" + impl + "FastDecompressor"); + safeDecompressor = classInstance("net.jpountz.lz4.LZ4" + impl + "SafeDecompressor"); + Constructor highConstructor = highCompressor.getClass().getDeclaredConstructor(int.class); + highCompressors[DEFAULT_COMPRESSION_LEVEL] = highCompressor; + for(int level = 1; level <= MAX_COMPRESSION_LEVEL; level++) { + if(level == 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(); + } + } + + } + + /** Return a blazing fast {@link LZ4Compressor}. */ + public LZ4Compressor fastCompressor() { + return fastCompressor; + } + + /** Return a {@link LZ4Compressor} which requires more memory than + * {@link #fastCompressor()} and is slower but compresses more efficiently. */ + public LZ4Compressor highCompressor() { + return highCompressor; + } + + /** Return 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. + *

+ */ + public LZ4Compressor highCompressor(int compressionLevel) { + if(compressionLevel > MAX_COMPRESSION_LEVEL) { + compressionLevel = MAX_COMPRESSION_LEVEL; + } else if(compressionLevel < 1) { + compressionLevel = DEFAULT_COMPRESSION_LEVEL; + } + return highCompressors[compressionLevel]; + } + + /** Return a {@link LZ4FastDecompressor} instance. */ + public LZ4FastDecompressor fastDecompressor() { + return fastDecompressor; + } + + /** Return a {@link LZ4SafeDecompressor} instance. */ + public LZ4SafeDecompressor safeDecompressor() { + return safeDecompressor; + } + + /** Return a {@link LZ4UnknownSizeDecompressor} instance. + * @deprecated use {@link #safeDecompressor()} */ + public LZ4UnknownSizeDecompressor unknownSizeDecompressor() { + return safeDecompressor(); + } + + /** Return a {@link LZ4Decompressor} instance. + * @deprecated use {@link #fastDecompressor()} */ + public LZ4Decompressor decompressor() { + return fastDecompressor(); + } + + /** Prints the fastest instance. */ + 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/core/src/main/java/net/jpountz/lz4/LZ4FastDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4FastDecompressor.java new file mode 100644 index 00000000..8427cc6e --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4FastDecompressor.java @@ -0,0 +1,106 @@ +package 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 { + + /** Decompress src[srcOff:] into dest[destOff:destOff+destLen] + * and return the number of bytes read from src. + * destLen must be exactly the size of the decompressed data. + * + * @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); + + /** Decompress src[srcOff:] into dest[destOff:destOff+destLen] + * and return 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 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)}. + */ + 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)}. + */ + 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;
+   * 
+ */ + 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)}. + */ + public final byte[] decompress(byte[] src, int destLen) { + return decompress(src, 0, destLen); + } + + /** + * Decompress 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. + */ + 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/core/src/main/java/net/jpountz/lz4/LZ4HCJNICompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4HCJNICompressor.java new file mode 100644 index 00000000..da721e31 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4HCJNICompressor.java @@ -0,0 +1,88 @@ +package 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 net.jpountz.lz4.LZ4Constants.DEFAULT_COMPRESSION_LEVEL; + +import java.nio.ByteBuffer; + +import net.jpountz.util.ByteBufferUtils; +import 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(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/core/src/main/java/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java new file mode 100644 index 00000000..f4ec3bd8 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java @@ -0,0 +1,550 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; +import static net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import net.jpountz.lz4.LZ4Utils.Match; +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java new file mode 100644 index 00000000..0f503221 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java @@ -0,0 +1,550 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; +import static net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import net.jpountz.lz4.LZ4Utils.Match; +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4InputStream.java b/core/src/main/java/net/jpountz/lz4/LZ4InputStream.java new file mode 100644 index 00000000..012c8071 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4InputStream.java @@ -0,0 +1,133 @@ +package net.jpountz.lz4; + +import java.io.IOException; +import java.io.InputStream; + +public class LZ4InputStream extends InputStream { + + private static LZ4Factory factory = LZ4Factory.fastestInstance(); + + private final InputStream inputStream; + private final LZ4Decompressor decompressor; + + private byte compressedBuffer[] = new byte[1048576]; + private byte decompressedBuffer[] = new byte[1048576]; + private int decompressedBufferPosition = 0; + private int decompressedBufferLength = 0; + + public LZ4InputStream(InputStream stream) { + this.decompressor = factory.decompressor(); + this.inputStream = stream; + } + + @Override + public void close() throws IOException { + inputStream.close(); + } + + @Override + public int read() throws IOException { + if (ensureBytesAvailableInDecompressedBuffer()) + return decompressedBuffer[decompressedBufferPosition++] & 0xFF; + + return -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (!ensureBytesAvailableInDecompressedBuffer()) + return -1; + + int numBytesRemainingToRead = len - off; + + while (numBytesRemainingToRead > 0 && ensureBytesAvailableInDecompressedBuffer()) { + int numBytesToRead = numBytesRemainingToRead; + int numBytesRemainingInBlock = decompressedBufferLength - decompressedBufferPosition; + if (numBytesToRead > numBytesRemainingInBlock) { + numBytesToRead = numBytesRemainingInBlock; + } + + System.arraycopy(decompressedBuffer, decompressedBufferPosition, b, off, numBytesToRead); + + decompressedBufferPosition += numBytesToRead; + off += numBytesToRead; + numBytesRemainingToRead -= numBytesToRead; + } + + return len - numBytesRemainingToRead; + } + + @Override + public long skip(long n) throws IOException { + long numBytesRemainingToSkip = n; + + while (numBytesRemainingToSkip > 0 && ensureBytesAvailableInDecompressedBuffer()) { + long numBytesToSkip = numBytesRemainingToSkip; + int numBytesRemainingInBlock = decompressedBufferLength - decompressedBufferPosition; + if (numBytesToSkip > numBytesRemainingInBlock) { + numBytesToSkip = numBytesRemainingInBlock; + } + + numBytesRemainingToSkip -= numBytesToSkip; + decompressedBufferPosition += numBytesToSkip; + } + + return n - numBytesRemainingToSkip; + } + + private boolean ensureBytesAvailableInDecompressedBuffer() throws IOException { + while (decompressedBufferPosition >= decompressedBufferLength) { + if (!fillBuffer()) { + return false; + } + } + + return true; + } + + private boolean fillBuffer() throws IOException { + decompressedBufferLength = LZ4StreamHelper.readLength(inputStream); + int compressedBufferLength = LZ4StreamHelper.readLength(inputStream); + + if (blockHeadersIndicateNoMoreData(compressedBufferLength, decompressedBufferLength)) { + return false; + } + + ensureBufferCapacity(compressedBufferLength, decompressedBufferLength); + + if (fillCompressedBuffer(compressedBufferLength)) { + decompressor.decompress(compressedBuffer, 0, decompressedBuffer, 0, decompressedBufferLength); + decompressedBufferPosition = 0; + return true; + } + + return false; + } + + private boolean blockHeadersIndicateNoMoreData(int compressedBufferLength, int decompressedBufferLength) { + return compressedBufferLength < 0 || decompressedBufferLength < 0; + } + + private boolean fillCompressedBuffer(int compressedBufferLength) throws IOException { + int bytesRead = 0; + while (bytesRead < compressedBufferLength) { + int bytesReadInAttempt = inputStream.read(compressedBuffer, bytesRead, compressedBufferLength - bytesRead); + if (bytesReadInAttempt < 0) + return false; + bytesRead += bytesReadInAttempt; + } + + return true; + } + + private void ensureBufferCapacity(int compressedBufferLength, int decompressedBufferLength) { + if (compressedBufferLength > compressedBuffer.length) { + compressedBuffer = new byte[compressedBufferLength]; + } + + if (decompressedBufferLength > decompressedBuffer.length) { + decompressedBuffer = new byte[decompressedBufferLength]; + } + } + +} \ No newline at end of file diff --git a/core/src/main/java/net/jpountz/lz4/LZ4JNI.java b/core/src/main/java/net/jpountz/lz4/LZ4JNI.java new file mode 100644 index 00000000..f083319a --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JNI.java @@ -0,0 +1,41 @@ +package 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 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/core/src/main/java/net/jpountz/lz4/LZ4JNICompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JNICompressor.java new file mode 100644 index 00000000..18971a33 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JNICompressor.java @@ -0,0 +1,80 @@ +package 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 net.jpountz.util.ByteBufferUtils.checkNotReadOnly; +import static net.jpountz.util.ByteBufferUtils.checkRange; +import static 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/core/src/main/java/net/jpountz/lz4/LZ4JNIFastDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JNIFastDecompressor.java new file mode 100644 index 00000000..5c355d85 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JNIFastDecompressor.java @@ -0,0 +1,82 @@ +package 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 net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4JNISafeDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JNISafeDecompressor.java new file mode 100644 index 00000000..d948c988 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JNISafeDecompressor.java @@ -0,0 +1,81 @@ +package 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 net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeCompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeCompressor.java new file mode 100644 index 00000000..06d635d6 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeCompressor.java @@ -0,0 +1,511 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; +import static net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java new file mode 100644 index 00000000..f9354d67 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java @@ -0,0 +1,205 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java new file mode 100644 index 00000000..2e77203c --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java @@ -0,0 +1,213 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java new file mode 100644 index 00000000..b52fb521 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java @@ -0,0 +1,511 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; +import static net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java new file mode 100644 index 00000000..ca26a4a8 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java @@ -0,0 +1,205 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java new file mode 100644 index 00000000..2bf83e51 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java @@ -0,0 +1,213 @@ +// Auto-generated: DO NOT EDIT + +package net.jpountz.lz4; + +import static net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import net.jpountz.util.ByteBufferUtils; +import 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/core/src/main/java/net/jpountz/lz4/LZ4OutputStream.java b/core/src/main/java/net/jpountz/lz4/LZ4OutputStream.java new file mode 100644 index 00000000..af16abab --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4OutputStream.java @@ -0,0 +1,73 @@ +package net.jpountz.lz4; + +import java.io.OutputStream; +import java.io.IOException; + +public class LZ4OutputStream extends OutputStream { + private static final LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); + private final LZ4Compressor compressor; + private static final int ONE_MEGABYTE = 1048576; + private final byte[] compressionInputBuffer; + private final byte[] compressionOutputBuffer; + private final OutputStream underlyingOutputStream; + private int bytesRemainingInCompressionInputBuffer = 0; + private int currentCompressionInputBufferPosition = 0; + + public LZ4OutputStream(OutputStream os) throws IOException { + this(os, ONE_MEGABYTE, lz4Factory.fastCompressor()); + } + + public LZ4OutputStream(OutputStream underlyingOutputStream, int blocksize, LZ4Compressor compressor) throws IOException { + compressionInputBuffer = new byte[blocksize]; + this.compressor = compressor; + this.underlyingOutputStream = underlyingOutputStream; + this.bytesRemainingInCompressionInputBuffer = blocksize; + this.currentCompressionInputBufferPosition = 0; + this.compressionOutputBuffer = new byte[compressor.maxCompressedLength(blocksize)]; + } + + public void write(byte[] b, int off, int len) throws IOException { + if (len <= bytesRemainingInCompressionInputBuffer) { + System.arraycopy(b, off, compressionInputBuffer, currentCompressionInputBufferPosition, len); + currentCompressionInputBufferPosition += len; + bytesRemainingInCompressionInputBuffer -= len; + } else { + // len > bytesRemainingInCompressionInputBuffer + while (len > 0) { + int bytesToCopy = Math.min(bytesRemainingInCompressionInputBuffer, len); + System.arraycopy(b, off, compressionInputBuffer, currentCompressionInputBufferPosition, bytesToCopy); + currentCompressionInputBufferPosition += bytesToCopy; + bytesRemainingInCompressionInputBuffer -= bytesToCopy; + flush(); + len -= bytesToCopy; + off += bytesToCopy; + } + } + } + + public void write(int i) throws IOException { + byte b = (byte)i; + if (0 == bytesRemainingInCompressionInputBuffer) { + flush(); + } + compressionInputBuffer[currentCompressionInputBufferPosition] = b; + bytesRemainingInCompressionInputBuffer--; + currentCompressionInputBufferPosition++; + } + + public void flush() throws IOException { + if(currentCompressionInputBufferPosition > 0) { + LZ4StreamHelper.writeLength(currentCompressionInputBufferPosition, this.underlyingOutputStream); + int bytesCompressed = compressor.compress(compressionInputBuffer, 0, currentCompressionInputBufferPosition, compressionOutputBuffer, 0, compressionOutputBuffer.length); + LZ4StreamHelper.writeLength(bytesCompressed, this.underlyingOutputStream); + underlyingOutputStream.write(compressionOutputBuffer, 0, bytesCompressed); + bytesRemainingInCompressionInputBuffer = compressionInputBuffer.length; + currentCompressionInputBufferPosition = 0; + } + } + + public void close() throws IOException { + flush(); + underlyingOutputStream.close(); + } +} diff --git a/core/src/main/java/net/jpountz/lz4/LZ4SafeDecompressor.java b/core/src/main/java/net/jpountz/lz4/LZ4SafeDecompressor.java new file mode 100644 index 00000000..2416ccba --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4SafeDecompressor.java @@ -0,0 +1,117 @@ +package 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 { + + /** + * Decompress src[srcOff:srcLen] into + * dest[destOff:destOff+maxDestLen] and returns the number of + * decompressed bytes written into dest. + * + * @param srcLen the exact size of the compressed stream + * @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); + + /** + * Uncompress src[srcOff:srcLen] into + * dest[destOff:destOff+maxDestLen] and returns the number of + * decompressed bytes written into dest. + * + * @param srcLen the exact size of the compressed stream + * @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)}. + */ + 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)} + */ + 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;
+   * 
+ */ + 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)}. + */ + public final byte[] decompress(byte[] src, int maxDestLen) { + return decompress(src, 0, src.length, maxDestLen); + } + + /** + * Decompress 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. + */ + 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/core/src/main/java/net/jpountz/lz4/LZ4SafeUtils.java b/core/src/main/java/net/jpountz/lz4/LZ4SafeUtils.java new file mode 100644 index 00000000..7842bd77 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4SafeUtils.java @@ -0,0 +1,179 @@ +package 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 net.jpountz.lz4.LZ4Constants.LAST_LITERALS; +import static net.jpountz.lz4.LZ4Constants.ML_BITS; +import static net.jpountz.lz4.LZ4Constants.ML_MASK; +import static net.jpountz.lz4.LZ4Constants.RUN_MASK; +import 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/core/src/main/java/net/jpountz/lz4/LZ4StreamHelper.java b/core/src/main/java/net/jpountz/lz4/LZ4StreamHelper.java new file mode 100644 index 00000000..1f42692f --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4StreamHelper.java @@ -0,0 +1,36 @@ +package net.jpountz.lz4; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class LZ4StreamHelper { + static void writeLength(int length, OutputStream os) throws IOException { + int b1 = ((length & 0xff000000) >> 24); + int b2 = ((length & 0x00ff0000) >> 16); + int b3 = ((length & 0x0000ff00) >> 8); + int b4 = (length & 0xff0000ff); + os.write(b1); + os.write(b2); + os.write(b3); + os.write(b4); + } + + // network order, big endian, most significant byte first + // package scope + static int readLength(InputStream is) throws IOException { + int b1 = is.read(); + int b2 = is.read(); + int b3 = is.read(); + int b4 = is.read(); + + int length; + if((-1 == b1) || (-1 == b2) || (-1 == b3) || (-1 == b4)) { + length = -1; + } + else { + length = ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4); + } + return length; + } +} \ No newline at end of file diff --git a/core/src/main/java/net/jpountz/lz4/LZ4StreamTest.java b/core/src/main/java/net/jpountz/lz4/LZ4StreamTest.java new file mode 100644 index 00000000..ca2d7cf7 --- /dev/null +++ b/core/src/main/java/net/jpountz/lz4/LZ4StreamTest.java @@ -0,0 +1,150 @@ +package net.jpountz.lz4; + +import static junit.framework.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Random; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; + +public class LZ4StreamTest { + + private long seed; + private Random rand; + + private byte randomContent[]; + private byte compressedOutput[]; + + @Before + public void setUp() throws IOException { + seed = System.currentTimeMillis(); + rand = new Random(seed); + + int randomContentLength = rand.nextInt(10000000) + 10000000; + + randomContent = new byte[randomContentLength]; + rand.nextBytes(randomContent); + + compressContent(); + } + + private void compressContent() throws IOException { + ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream(); + + LZ4OutputStream os = new LZ4OutputStream(compressedOutputStream); + int currentContentPosition = 0; + + while(currentContentPosition < randomContent.length) { + int testBlockSize = rand.nextInt(500000); + + if(testBlockSize > randomContent.length - currentContentPosition) + testBlockSize = randomContent.length - currentContentPosition; + + boolean writeByteByByte = true; //rand.nextBoolean(); + + if(writeByteByByte) { + for(int i=0;i= 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/core/src/main/java/net/jpountz/lz4/package.html b/core/src/main/java/net/jpountz/lz4/package.html new file mode 100644 index 00000000..e5341067 --- /dev/null +++ b/core/src/main/java/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 net.jpountz.lz4.LZ4Compressor compressors} and +{@link 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/core/src/main/java/net/jpountz/util/ByteBufferUtils.java b/core/src/main/java/net/jpountz/util/ByteBufferUtils.java new file mode 100644 index 00000000..9e5f3388 --- /dev/null +++ b/core/src/main/java/net/jpountz/util/ByteBufferUtils.java @@ -0,0 +1,92 @@ +package 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/core/src/main/java/net/jpountz/util/LZ4UnsafeUtils.java b/core/src/main/java/net/jpountz/util/LZ4UnsafeUtils.java new file mode 100644 index 00000000..a5ad7834 --- /dev/null +++ b/core/src/main/java/net/jpountz/util/LZ4UnsafeUtils.java @@ -0,0 +1,206 @@ +package 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 net.jpountz.lz4.LZ4Constants.COPY_LENGTH; +import static net.jpountz.lz4.LZ4Constants.LAST_LITERALS; +import static net.jpountz.lz4.LZ4Constants.ML_BITS; +import static net.jpountz.lz4.LZ4Constants.ML_MASK; +import static net.jpountz.lz4.LZ4Constants.RUN_MASK; +import static net.jpountz.util.UnsafeUtils.readByte; +import static net.jpountz.util.UnsafeUtils.readInt; +import static net.jpountz.util.UnsafeUtils.readLong; +import static net.jpountz.util.UnsafeUtils.readShort; +import static net.jpountz.util.UnsafeUtils.writeByte; +import static net.jpountz.util.UnsafeUtils.writeInt; +import static net.jpountz.util.UnsafeUtils.writeLong; +import static net.jpountz.util.UnsafeUtils.writeShort; +import static net.jpountz.util.Utils.NATIVE_BYTE_ORDER; + +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) { + writeByte(dest, destOff + fastLen + i, 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) { + writeLong(dest, destOff + i, 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) { + 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 void safeIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchLen) { + for (int i = 0; i < matchLen; ++i) { + dest[dOff + i] = dest[matchOff + i]; + writeByte(dest, dOff + i, readByte(dest, matchOff + i)); + } + } + + static int readShortLittleEndian(byte[] src, int srcOff) { + short s = readShort(src, srcOff); + if (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 (NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + s = Short.reverseBytes(s); + } + writeShort(dest, destOff, s); + } + + static boolean readIntEquals(byte[] src, int ref, int sOff) { + return readInt(src, ref) == readInt(src, sOff); + } + + static int commonBytes(byte[] 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 (NATIVE_BYTE_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 writeLen(int len, byte[] dest, int dOff) { + while (len >= 0xFF) { + writeByte(dest, dOff++, 0xFF); + len -= 0xFF; + } + 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 >= 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 commonBytesBackward(byte[] b, int o1, int o2, int l1, int l2) { + int count = 0; + while (o1 > l1 && o2 > l2 && readByte(b, --o1) == 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/core/src/main/java/net/jpountz/util/Native.java b/core/src/main/java/net/jpountz/util/Native.java new file mode 100644 index 00000000..d4a8707b --- /dev/null +++ b/core/src/main/java/net/jpountz/util/Native.java @@ -0,0 +1,125 @@ +package 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 com.boydti.fawe.Fawe; +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; + + 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(); + return "/" + 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; + } + String resourceName = resourceName(); + System.out.println("NAME: " + resourceName); + System.out.println("WORK: " + Fawe.class.getResourceAsStream("/LICENSE")); + System.out.println("WORK: " + Fawe.class.getResourceAsStream("/win32/amd64/liblz4-java.so")); + InputStream is = Fawe.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/core/src/main/java/net/jpountz/util/SafeUtils.java b/core/src/main/java/net/jpountz/util/SafeUtils.java new file mode 100644 index 00000000..ceaf9177 --- /dev/null +++ b/core/src/main/java/net/jpountz/util/SafeUtils.java @@ -0,0 +1,95 @@ +package 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/core/src/main/java/net/jpountz/util/UnsafeUtils.java b/core/src/main/java/net/jpountz/util/UnsafeUtils.java new file mode 100644 index 00000000..30231ef1 --- /dev/null +++ b/core/src/main/java/net/jpountz/util/UnsafeUtils.java @@ -0,0 +1,147 @@ +package 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 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/core/src/main/java/net/jpountz/util/Utils.java b/core/src/main/java/net/jpountz/util/Utils.java new file mode 100644 index 00000000..12177bcf --- /dev/null +++ b/core/src/main/java/net/jpountz/util/Utils.java @@ -0,0 +1,35 @@ +package 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"); + } + + public static boolean isUnalignedAccessAllowed() { + return unalignedAccessAllowed; + } + +} diff --git a/core/src/main/java/net/jpountz/util/package.html b/core/src/main/java/net/jpountz/util/package.html new file mode 100644 index 00000000..4b3ceb98 --- /dev/null +++ b/core/src/main/java/net/jpountz/util/package.html @@ -0,0 +1,22 @@ + + + + + + + +

Utility classes.

+ + \ No newline at end of file diff --git a/core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java b/core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java new file mode 100644 index 00000000..3dd3ed4f --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java @@ -0,0 +1,42 @@ +package 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 static net.jpountz.xxhash.XXHashConstants.PRIME1; +import static net.jpountz.xxhash.XXHashConstants.PRIME2; + +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 + PRIME1 + PRIME2; + v2 = seed + PRIME2; + v3 = seed + 0; + v4 = seed - PRIME1; + totalLen = 0; + memSize = 0; + } + +} diff --git a/core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java b/core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java new file mode 100644 index 00000000..97e294dd --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java @@ -0,0 +1,43 @@ +package 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 static net.jpountz.xxhash.XXHashConstants.PRIME64_1; +import static net.jpountz.xxhash.XXHashConstants.PRIME64_2; + +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 + PRIME64_1 + PRIME64_2; + v2 = seed + PRIME64_2; + v3 = seed + 0; + v4 = seed - PRIME64_1; + totalLen = 0; + memSize = 0; + } + +} diff --git a/core/src/main/java/net/jpountz/xxhash/StreamingXXHash32.java b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash32.java new file mode 100644 index 00000000..c59f0346 --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash32.java @@ -0,0 +1,111 @@ +package 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; + } + + /** + * Get the value of the checksum. + */ + public abstract int getValue(); + + /** + * Update the value of the hash with buf[off:off+len]. + */ + public abstract void update(byte[] buf, int off, int len); + + /** + * Reset 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 + ")"; + } + + /** + * Return a {@link Checksum} view of this instance. Modifications to the view + * will modify this instance too and vice-versa. + */ + 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(); + } + + }; + } + +} \ No newline at end of file diff --git a/core/src/main/java/net/jpountz/xxhash/StreamingXXHash32JNI.java b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash32JNI.java new file mode 100644 index 00000000..e9b58fb4 --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash32JNI.java @@ -0,0 +1,71 @@ +package 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/core/src/main/java/net/jpountz/xxhash/StreamingXXHash64.java b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash64.java new file mode 100644 index 00000000..c68fb4d1 --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash64.java @@ -0,0 +1,111 @@ +package 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; + } + + /** + * Get the value of the checksum. + */ + public abstract long getValue(); + + /** + * Update the value of the hash with buf[off:off+len]. + */ + public abstract void update(byte[] buf, int off, int len); + + /** + * Reset 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 + ")"; + } + + /** + * Return a {@link Checksum} view of this instance. Modifications to the view + * will modify this instance too and vice-versa. + */ + 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(); + } + + }; + } + +} \ No newline at end of file diff --git a/core/src/main/java/net/jpountz/xxhash/StreamingXXHash64JNI.java b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash64JNI.java new file mode 100644 index 00000000..b7ab1acd --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/StreamingXXHash64JNI.java @@ -0,0 +1,71 @@ +package 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/core/src/main/java/net/jpountz/xxhash/XXHash32.java b/core/src/main/java/net/jpountz/xxhash/XXHash32.java new file mode 100644 index 00000000..5d211436 --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/XXHash32.java @@ -0,0 +1,55 @@ +package 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 { + + /** + * Compute the 32-bits hash of buf[off:off+len] using seed + * seed. + */ + public abstract int hash(byte[] buf, int off, int len, int seed); + + /** + * Compute the hash of the given slice of the {@link ByteBuffer}. + * {@link ByteBuffer#position() position} and {@link ByteBuffer#limit() limit} + * are not modified. + */ + public abstract int hash(ByteBuffer buf, int off, int len, int seed); + + /** + * Compute the hash of the given {@link ByteBuffer}. The + * {@link ByteBuffer#position() position} is moved in order to reflect bytes + * which have been read. + */ + 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/core/src/main/java/net/jpountz/xxhash/XXHash32JNI.java b/core/src/main/java/net/jpountz/xxhash/XXHash32JNI.java new file mode 100644 index 00000000..4b713d11 --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/XXHash32JNI.java @@ -0,0 +1,49 @@ +package 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 static net.jpountz.util.ByteBufferUtils.checkRange; +import static 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) { + 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()) { + 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/core/src/main/java/net/jpountz/xxhash/XXHash64.java b/core/src/main/java/net/jpountz/xxhash/XXHash64.java new file mode 100644 index 00000000..3ab73ddc --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/XXHash64.java @@ -0,0 +1,55 @@ +package 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 { + + /** + * Compute the 64-bits hash of buf[off:off+len] using seed + * seed. + */ + public abstract long hash(byte[] buf, int off, int len, long seed); + + /** + * Compute the hash of the given slice of the {@link ByteBuffer}. + * {@link ByteBuffer#position() position} and {@link ByteBuffer#limit() limit} + * are not modified. + */ + public abstract long hash(ByteBuffer buf, int off, int len, long seed); + + /** + * Compute the hash of the given {@link ByteBuffer}. The + * {@link ByteBuffer#position() position} is moved in order to reflect bytes + * which have been read. + */ + 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/core/src/main/java/net/jpountz/xxhash/XXHash64JNI.java b/core/src/main/java/net/jpountz/xxhash/XXHash64JNI.java new file mode 100644 index 00000000..d952e1e5 --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/XXHash64JNI.java @@ -0,0 +1,49 @@ +package 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 static net.jpountz.util.ByteBufferUtils.checkRange; +import static 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) { + 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()) { + 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/core/src/main/java/net/jpountz/xxhash/XXHashConstants.java b/core/src/main/java/net/jpountz/xxhash/XXHashConstants.java new file mode 100644 index 00000000..2e887dfd --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/XXHashConstants.java @@ -0,0 +1,31 @@ +package 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/core/src/main/java/net/jpountz/xxhash/XXHashFactory.java b/core/src/main/java/net/jpountz/xxhash/XXHashFactory.java new file mode 100644 index 00000000..ab03dff1 --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/XXHashFactory.java @@ -0,0 +1,220 @@ +package 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 net.jpountz.util.Native; +import 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; + + /** Return 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. + *
+ */ + public static synchronized XXHashFactory nativeInstance() { + if (NATIVE_INSTANCE == null) { + NATIVE_INSTANCE = instance("JNI"); + } + return NATIVE_INSTANCE; + } + + /** 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; + } + + /** 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; + } + + /** + * Return 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}. + */ + public static XXHashFactory fastestJavaInstance() { + if (Utils.isUnalignedAccessAllowed()) { + try { + return unsafeInstance(); + } catch (Throwable t) { + return safeInstance(); + } + } else { + return safeInstance(); + } + } + + /** + * Return 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. + */ + 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("net.jpountz.xxhash.XXHash32" + impl); + streamingHash32Factory = classInstance("net.jpountz.xxhash.StreamingXXHash32" + impl + "$Factory"); + hash64 = classInstance("net.jpountz.xxhash.XXHash64" + impl); + streamingHash64Factory = classInstance("net.jpountz.xxhash.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(); + } + } + + /** Return a {@link XXHash32} instance. */ + public XXHash32 hash32() { + return hash32; + } + + /** Return a {@link XXHash64} instance. */ + public XXHash64 hash64() { + return hash64; + } + + /** + * Return a new {@link StreamingXXHash32} instance. + */ + public StreamingXXHash32 newStreamingHash32(int seed) { + return streamingHash32Factory.newStreamingHash(seed); + } + + /** + * Return a new {@link StreamingXXHash64} instance. + */ + public StreamingXXHash64 newStreamingHash64(long seed) { + return streamingHash64Factory.newStreamingHash(seed); + } + + /** Prints the fastest instance. */ + 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/core/src/main/java/net/jpountz/xxhash/XXHashJNI.java b/core/src/main/java/net/jpountz/xxhash/XXHashJNI.java new file mode 100644 index 00000000..125e640b --- /dev/null +++ b/core/src/main/java/net/jpountz/xxhash/XXHashJNI.java @@ -0,0 +1,43 @@ +package 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 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/core/src/main/java/net/jpountz/xxhash/package.html b/core/src/main/java/net/jpountz/xxhash/package.html new file mode 100644 index 00000000..f595d25a --- /dev/null +++ b/core/src/main/java/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();
+
+ + + diff --git a/core/src/main/resources/darwin/x86_64/liblz4-java.dylib b/core/src/main/resources/darwin/x86_64/liblz4-java.dylib new file mode 100644 index 0000000000000000000000000000000000000000..9d5cc0e58e3a99ce201dae544636959c8bfdccd6 GIT binary patch literal 47188 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 M8J~N32OwAeZ}C<7HUIzs literal 0 HcmV?d00001 diff --git a/core/src/main/resources/linux/amd64/liblz4-java.so b/core/src/main/resources/linux/amd64/liblz4-java.so new file mode 100644 index 0000000000000000000000000000000000000000..fa143b1eae21cb08fa80f0b1c015b18c0cd62488 GIT binary patch literal 59545 zcmeEv349b))^B$bAYi0g2oP~0I<2N%FcH+WfTpoqs<0ZOfS}?41|m@;G3g|XA3~tB z)zq}+jW{~a#y9HBe9i(h;~?(o07(b}0b~&uB8qeomH-N2&HJBwZ*^BfV3==t@ArPc z*U+iDOWnHn+_T+t&b__Rr`{K5x7*B5yzOoqrLwQ#Ia#70t3ll0l4Z-Z^~B$+Y=c?d z9Xo^M{$;#oCYmbjYF#fj%q#dE3RIrG{~4dY{8 z6M52>fGd4#M0wY%Q`9rBRF^*4djd1*e(7IX@|^2x>JUEW)qL&&JV)cZ*pIcJ$#{r{ zJ!ae_+t6jrpG5K!zN&t!cvjAfDYre4*m08DoPqBwy1{1}zu4U3+Vyg&C1ZfNe;C)> z@Ewcqb@<+a?=XBH#CHn5^zq?42;ax>?Thbde9ccPZl;R_jk%AwACGGWzN$#yBCd3~ z+j2ip+$UM`CgPsPW&QLL2@^h)yaM0*@V%1~_{jXi{$7i}m*abr$jid@5`0tey&c~h z@x2CL`VgEm@$HT81NhQs9KH&^S5iWJa&Vo5?`8NpMcxl_oht6TULHk8e|)>6i&3I- zy5*sVa6i(LHg9jh%@}+okB(xUH-|d%BrJt-hSob*(dIv`NFdI2H&@LcHcLDG4%0Ahi~qe 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 literal 0 HcmV?d00001 diff --git a/core/src/main/resources/linux/i386/liblz4-java.so b/core/src/main/resources/linux/i386/liblz4-java.so new file mode 100644 index 0000000000000000000000000000000000000000..aa50fd15b9283391987011af38671d7e6e8c83d2 GIT binary patch literal 68840 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 Z3v>nbEX%RRt*w@EJkF^_Tp-k({|oERv|s=L literal 0 HcmV?d00001 diff --git a/forge/.gradle/gradle.log b/forge/.gradle/gradle.log index 8fbb2e8b..e69de29b 100644 --- a/forge/.gradle/gradle.log +++ b/forge/.gradle/gradle.log @@ -1,88 +0,0 @@ -################################################# - ForgeGradle 2.1-SNAPSHOT-unknown - https://github.com/MinecraftForge/ForgeGradle -################################################# - Powered by MCP unknown - http://modcoderpack.com - by: Searge, ProfMobius, Fesh0r, - R4wk, ZeuX, IngisKahn, bspkrs -################################################# -Version string 'unspecified' does not match SemVer specification -You should try SemVer : http://semver.org/ -:core:compileJava -:forge:deobfCompileDummyTask -:forge:deobfProvidedDummyTask -:forge:extractDependencyATs SKIPPED -:forge:extractMcpData -:core:compileJava UP-TO-DATE -:core:processResources UP-TO-DATE -:core:classes UP-TO-DATE -:core:jar UP-TO-DATE -:forge:extractMcpData SKIPPED -:forge:extractMcpMappings SKIPPED -:forge:genSrgs SKIPPED -:forge:getVersionJson -:forge:downloadServer SKIPPED -:forge:splitServerJar SKIPPED -:forge:deobfMcMCP SKIPPED -:forge:sourceApiJava UP-TO-DATE -:forge:compileApiJava UP-TO-DATE -:forge:processApiResources UP-TO-DATE -:forge:apiClasses UP-TO-DATE -:forge:sourceMainJava UP-TO-DATE -:forge:compileJavaC:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'modid()' in type 'Mod': class file for net.minecraftforge.fml.common.Mod not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'name()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'version()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptableRemoteVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'dependencies()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptedMinecraftVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'value()' in type 'Instance': class file for net.minecraftforge.fml.common.Mod$Instance not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'serverSide()' in type 'SidedProxy': class file for net.minecraftforge.fml.common.SidedProxy not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'clientSide()' in type 'SidedProxy' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'modid()' in type 'Mod': class file for net.minecraftforge.fml.common.Mod not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'name()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'version()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptableRemoteVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'dependencies()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptedMinecraftVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'value()' in type 'Instance': class file for net.minecraftforge.fml.common.Mod$Instance not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'serverSide()' in type 'SidedProxy': class file for net.minecraftforge.fml.common.SidedProxy not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'clientSide()' in type 'SidedProxy' -Note: Writing plugin metadata to file:/C:/Users/Jesse/Desktop/OTHER/GitHub/FastAsyncWorldEdit/forge/build/classes/main/mcmod.info -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'modid()' in type 'Mod': class file for net.minecraftforge.fml.common.Mod not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'name()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'version()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptableRemoteVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'dependencies()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptedMinecraftVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'value()' in type 'Instance': class file for net.minecraftforge.fml.common.Mod$Instance not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'serverSide()' in type 'SidedProxy': class file for net.minecraftforge.fml.common.SidedProxy not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'clientSide()' in type 'SidedProxy' -Note: Some input files use or override a deprecated API. -Note: Recompile with -Xlint:deprecation for details. -Note: Some input files use unchecked or unsafe operations. -Note: Recompile with -Xlint:unchecked for details. -27 warnings - -:forge:processResources UP-TO-DATE -:forge:classes -:forge:jar -:forge:sourceTestJava UP-TO-DATE -:forge:compileTestJava UP-TO-DATE -:forge:processTestResources UP-TO-DATE -:forge:testClasses UP-TO-DATE -:forge:test UP-TO-DATE -:forge:reobfJar -:forge:shadowJar -:forge:reobfShadowJar -:forge:extractRangemapReplacedMain -C:\Users\Jesse\Desktop\OTHER\GitHub\FastAsyncWorldEdit\forge\build\sources\main\java -:forge:retromapReplacedMain UP-TO-DATE -:forge:sourceJar UP-TO-DATE -:forge:assemble -:forge:check UP-TO-DATE -:forge:build - -BUILD SUCCESSFUL - -Total time: 36.184 secs diff --git a/gradle.properties b/gradle.properties index 0ff1aa36..5a95e1a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.daemon=true +org.gradle.daemon=false org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.configureondemand=true org.gradle.parallel=true \ No newline at end of file