1 package net.jpountz.lz4;
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 import java.lang.reflect.Constructor;
18 import java.lang.reflect.Field;
19 import java.lang.reflect.InvocationTargetException;
20 import java.util.Arrays;
22 import net.jpountz.util.Native;
23 import net.jpountz.util.Utils;
24 import static net.jpountz.lz4.LZ4Constants.DEFAULT_COMPRESSION_LEVEL;
25 import static net.jpountz.lz4.LZ4Constants.MAX_COMPRESSION_LEVEL;
28 * Entry point for the LZ4 API.
30 * This class has 3 instances<ul>
31 * <li>a {@link #nativeInstance() native} instance which is a JNI binding to
32 * <a href="http://code.google.com/p/lz4/">the original LZ4 C implementation</a>.
33 * <li>a {@link #safeInstance() safe Java} instance which is a pure Java port
34 * of the original C library,</li>
35 * <li>an {@link #unsafeInstance() unsafe Java} instance which is a Java port
36 * using the unofficial {@link sun.misc.Unsafe} API.
39 * Only the {@link #safeInstance() safe instance} is guaranteed to work on your
40 * JVM, as a consequence it is advised to use the {@link #fastestInstance()} or
41 * {@link #fastestJavaInstance()} to pull a {@link LZ4Factory} instance.
43 * All methods from this class are very costly, so you should get an instance
44 * once, and then reuse it whenever possible. This is typically done by storing
45 * a {@link LZ4Factory} instance in a static field.
47 public final class LZ4Factory {
49 private static LZ4Factory instance(String impl) {
51 return new LZ4Factory(impl);
52 } catch (Exception e) {
53 throw new AssertionError(e);
57 private static LZ4Factory NATIVE_INSTANCE,
62 * Return a {@link LZ4Factory} instance that returns compressors and
63 * decompressors that are native bindings to the original C library.
65 * Please note that this instance has some traps you should be aware of:<ol>
66 * <li>Upon loading this instance, files will be written to the temporary
67 * directory of the system. Although these files are supposed to be deleted
68 * when the JVM exits, they might remain on systems that don't support
69 * removal of files being used such as Windows.
70 * <li>The instance can only be loaded once per JVM. This can be a problem
71 * if your application uses multiple class loaders (such as most servlet
72 * containers): this instance will only be available to the children of the
73 * class loader which has loaded it. As a consequence, it is advised to
74 * either not use this instance in webapps or to put this library in the lib
75 * directory of your servlet container so that it is loaded by the system
79 public static synchronized LZ4Factory nativeInstance() {
80 if (NATIVE_INSTANCE == null) {
81 NATIVE_INSTANCE = instance("JNI");
83 return NATIVE_INSTANCE;
86 /** Return a {@link LZ4Factory} instance that returns compressors and
87 * decompressors that are written with Java's official API. */
88 public static synchronized LZ4Factory safeInstance() {
89 if (JAVA_SAFE_INSTANCE == null) {
90 JAVA_SAFE_INSTANCE = instance("JavaSafe");
92 return JAVA_SAFE_INSTANCE;
95 /** Return a {@link LZ4Factory} instance that returns compressors and
96 * decompressors that may use {@link sun.misc.Unsafe} to speed up compression
97 * and decompression. */
98 public static synchronized LZ4Factory unsafeInstance() {
99 if (JAVA_UNSAFE_INSTANCE == null) {
100 JAVA_UNSAFE_INSTANCE = instance("JavaUnsafe");
102 return JAVA_UNSAFE_INSTANCE;
106 * Return the fastest available {@link LZ4Factory} instance which does not
107 * rely on JNI bindings. It first tries to load the
108 * {@link #unsafeInstance() unsafe instance}, and then the
109 * {@link #safeInstance() safe Java instance} if the JVM doesn't have a
110 * working {@link sun.misc.Unsafe}.
112 public static LZ4Factory fastestJavaInstance() {
113 if (Utils.isUnalignedAccessAllowed()) {
115 return unsafeInstance();
116 } catch (Throwable t) {
117 return safeInstance();
120 return safeInstance();
125 * Return the fastest available {@link LZ4Factory} instance. If the class
126 * loader is the system class loader and if the
127 * {@link #nativeInstance() native instance} loads successfully, then the
128 * {@link #nativeInstance() native instance} is returned, otherwise the
129 * {@link #fastestJavaInstance() fastest Java instance} is returned.
131 * Please read {@link #nativeInstance() javadocs of nativeInstance()} before
134 public static LZ4Factory fastestInstance() {
135 if (Native.isLoaded()
136 || Native.class.getClassLoader() == ClassLoader.getSystemClassLoader()) {
138 return nativeInstance();
139 } catch (Throwable t) {
140 return fastestJavaInstance();
143 return fastestJavaInstance();
147 @SuppressWarnings("unchecked")
148 private static <T> T classInstance(String cls) throws NoSuchFieldException, SecurityException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException {
149 ClassLoader loader = LZ4Factory.class.getClassLoader();
150 loader = loader == null ? ClassLoader.getSystemClassLoader() : loader;
151 final Class<?> c = loader.loadClass(cls);
152 Field f = c.getField("INSTANCE");
153 return (T) f.get(null);
156 private final String impl;
157 private final LZ4Compressor fastCompressor;
158 private final LZ4Compressor highCompressor;
159 private final LZ4FastDecompressor fastDecompressor;
160 private final LZ4SafeDecompressor safeDecompressor;
161 private final LZ4Compressor[] highCompressors = new LZ4Compressor[MAX_COMPRESSION_LEVEL+1];
163 private LZ4Factory(String impl) throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException {
165 fastCompressor = classInstance("net.jpountz.lz4.LZ4" + impl + "Compressor");
166 highCompressor = classInstance("net.jpountz.lz4.LZ4HC" + impl + "Compressor");
167 fastDecompressor = classInstance("net.jpountz.lz4.LZ4" + impl + "FastDecompressor");
168 safeDecompressor = classInstance("net.jpountz.lz4.LZ4" + impl + "SafeDecompressor");
169 Constructor<? extends LZ4Compressor> highConstructor = highCompressor.getClass().getDeclaredConstructor(int.class);
170 highCompressors[DEFAULT_COMPRESSION_LEVEL] = highCompressor;
171 for(int level = 1; level <= MAX_COMPRESSION_LEVEL; level++) {
172 if(level == DEFAULT_COMPRESSION_LEVEL) continue;
173 highCompressors[level] = highConstructor.newInstance(level);
176 // quickly test that everything works as expected
177 final byte[] original = new byte[] {'a','b','c','d',' ',' ',' ',' ',' ',' ','a','b','c','d','e','f','g','h','i','j'};
178 for (LZ4Compressor compressor : Arrays.asList(fastCompressor, highCompressor)) {
179 final int maxCompressedLength = compressor.maxCompressedLength(original.length);
180 final byte[] compressed = new byte[maxCompressedLength];
181 final int compressedLength = compressor.compress(original, 0, original.length, compressed, 0, maxCompressedLength);
182 final byte[] restored = new byte[original.length];
183 fastDecompressor.decompress(compressed, 0, restored, 0, original.length);
184 if (!Arrays.equals(original, restored)) {
185 throw new AssertionError();
187 Arrays.fill(restored, (byte) 0);
188 final int decompressedLength = safeDecompressor.decompress(compressed, 0, compressedLength, restored, 0);
189 if (decompressedLength != original.length || !Arrays.equals(original, restored)) {
190 throw new AssertionError();
196 /** Return a blazing fast {@link LZ4Compressor}. */
197 public LZ4Compressor fastCompressor() {
198 return fastCompressor;
201 /** Return a {@link LZ4Compressor} which requires more memory than
202 * {@link #fastCompressor()} and is slower but compresses more efficiently. */
203 public LZ4Compressor highCompressor() {
204 return highCompressor;
207 /** Return a {@link LZ4Compressor} which requires more memory than
208 * {@link #fastCompressor()} and is slower but compresses more efficiently.
209 * The compression level can be customized.
210 * <p>For current implementations, the following is true about compression level:<ol>
211 * <li>It should be in range [1, 17]</li>
212 * <li>A compression level higher than 17 would be treated as 17.</li>
213 * <li>A compression level lower than 1 would be treated as 9.</li>
216 public LZ4Compressor highCompressor(int compressionLevel) {
217 if(compressionLevel > MAX_COMPRESSION_LEVEL) {
218 compressionLevel = MAX_COMPRESSION_LEVEL;
219 } else if(compressionLevel < 1) {
220 compressionLevel = DEFAULT_COMPRESSION_LEVEL;
222 return highCompressors[compressionLevel];
225 /** Return a {@link LZ4FastDecompressor} instance. */
226 public LZ4FastDecompressor fastDecompressor() {
227 return fastDecompressor;
230 /** Return a {@link LZ4SafeDecompressor} instance. */
231 public LZ4SafeDecompressor safeDecompressor() {
232 return safeDecompressor;
235 /** Return a {@link LZ4UnknownSizeDecompressor} instance.
236 * @deprecated use {@link #safeDecompressor()} */
237 public LZ4UnknownSizeDecompressor unknownSizeDecompressor() {
238 return safeDecompressor();
241 /** Return a {@link LZ4Decompressor} instance.
242 * @deprecated use {@link #fastDecompressor()} */
243 public LZ4Decompressor decompressor() {
244 return fastDecompressor();
247 /** Prints the fastest instance. */
248 public static void main(String[] args) {
249 System.out.println("Fastest instance is " + fastestInstance());
250 System.out.println("Fastest Java instance is " + fastestJavaInstance());
254 public String toString() {
255 return getClass().getSimpleName() + ":" + impl;