diff options
author | Eugene Kliuchnikov <eustas.ru@gmail.com> | 2021-08-18 19:15:07 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-18 19:15:07 +0200 |
commit | 68f1b90ad0d204907beb58304d0bd06391001a4d (patch) | |
tree | 15fcfe4d6dd5a3058f49e8a6c680afb2ec4c97d0 /java | |
parent | 19d86fb9a60aa7034d4981b69a5b656f5b90017e (diff) | |
download | brotli-68f1b90ad0d204907beb58304d0bd06391001a4d.zip brotli-68f1b90ad0d204907beb58304d0bd06391001a4d.tar.gz brotli-68f1b90ad0d204907beb58304d0bd06391001a4d.tar.bz2 |
Update (#918)
Prepare to use copybara worklow.
Diffstat (limited to 'java')
-rw-r--r-- | java/org/brotli/dec/BUILD | 9 | ||||
-rw-r--r-- | java/org/brotli/dec/Decoder.java | 61 | ||||
-rw-r--r-- | java/org/brotli/integration/Benchmark.java | 117 | ||||
-rw-r--r-- | java/org/brotli/integration/test_data.zip | bin | 2859607 -> 2857520 bytes | |||
-rw-r--r-- | java/org/brotli/wrapper/android/JniHelper.java | 83 | ||||
-rw-r--r-- | java/org/brotli/wrapper/android/UseJni.java | 22 | ||||
-rw-r--r-- | java/org/brotli/wrapper/android/UseJniTest.java | 23 | ||||
-rw-r--r-- | java/org/brotli/wrapper/dec/BUILD | 7 | ||||
-rw-r--r-- | java/org/brotli/wrapper/dec/decoder_jni.cc | 2 | ||||
-rw-r--r-- | java/org/brotli/wrapper/dec/decoder_jni.h | 75 | ||||
-rw-r--r-- | java/org/brotli/wrapper/dec/decoder_jni_onload.cc | 55 | ||||
-rw-r--r-- | java/org/brotli/wrapper/enc/Encoder.java | 12 |
12 files changed, 459 insertions, 7 deletions
diff --git a/java/org/brotli/dec/BUILD b/java/org/brotli/dec/BUILD index 208642e..9257ea4 100644 --- a/java/org/brotli/dec/BUILD +++ b/java/org/brotli/dec/BUILD @@ -1,6 +1,8 @@ # Description: # Java port of Brotli decoder. +load(":build_defs.bzl", "brotli_java_test") + package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # MIT @@ -14,14 +16,15 @@ java_library( name = "dec", srcs = glob( ["*.java"], - exclude = ["*Test*.java"], + exclude = [ + "Decoder.java", + "*Test*.java", + ], ), proguard_specs = ["proguard.pgcfg"], resource_jars = ["//:license"], ) -load(":build_defs.bzl", "brotli_java_test") - brotli_java_test( name = "BitReaderTest", srcs = ["BitReaderTest.java"], diff --git a/java/org/brotli/dec/Decoder.java b/java/org/brotli/dec/Decoder.java new file mode 100644 index 0000000..599588e --- /dev/null +++ b/java/org/brotli/dec/Decoder.java @@ -0,0 +1,61 @@ +package org.brotli.dec; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class Decoder { + private static long decodeBytes(InputStream input, OutputStream output, byte[] buffer) + throws IOException { + long totalOut = 0; + int readBytes; + BrotliInputStream in = new BrotliInputStream(input); + in.enableLargeWindow(); + try { + while ((readBytes = in.read(buffer)) >= 0) { + output.write(buffer, 0, readBytes); + totalOut += readBytes; + } + } finally { + in.close(); + } + return totalOut; + } + + public static void main(String... args) throws IOException { + if (args.length != 2) { + System.out.println("Usage: decoder <compressed_in> <decompressed_out>"); + return; + } + + byte[] buffer = new byte[1024 * 1024]; + long start; + long bytesDecoded; + long end; + InputStream in = null; + OutputStream out = null; + try { + in = new FileInputStream(args[0]); + out = new FileOutputStream(args[1]); + start = System.nanoTime(); + bytesDecoded = decodeBytes(in, out, buffer); + end = System.nanoTime(); + } finally { + if (in != null) { + in.close(); // Hopefully, does not throw exception. + } + if (out != null) { + out.close(); + } + } + + double timeDelta = (end - start) / 1000000000.0; + if (timeDelta <= 0) { + return; + } + double mbDecoded = bytesDecoded / (1024.0 * 1024.0); + System.out.println(mbDecoded / timeDelta + " MiB/s"); + } +} diff --git a/java/org/brotli/integration/Benchmark.java b/java/org/brotli/integration/Benchmark.java new file mode 100644 index 0000000..d44e161 --- /dev/null +++ b/java/org/brotli/integration/Benchmark.java @@ -0,0 +1,117 @@ +package org.brotli.integration; + +import org.brotli.dec.BrotliInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + +/** + * Measures decompression speed on the given corpus. + */ +public class Benchmark { + private static byte[] readFile(String fileName) throws IOException { + int bufferLength = 65536; + byte[] buffer = new byte[bufferLength]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + FileInputStream fin = new FileInputStream(fileName); + try { + int bytesRead; + while ((bytesRead = fin.read(buffer)) >= 0) { + baos.write(buffer, 0, bytesRead); + } + } finally { + fin.close(); + } + return baos.toByteArray(); + } + + private static long decodeBytes(InputStream input, OutputStream output, byte[] buffer) + throws IOException { + long totalOut = 0; + int readBytes; + BrotliInputStream in = new BrotliInputStream(input); + try { + while ((readBytes = in.read(buffer)) >= 0) { + output.write(buffer, 0, readBytes); + totalOut += readBytes; + } + } finally { + in.close(); + } + return totalOut; + } + + public static void main(String... args) throws IOException { + if (args.length == 0) { + System.out.println("Usage: benchmark <corpus>"); + return; + } + File corpusDir = new File(args[0]); + ArrayList<String> fileNames = new ArrayList<String>(); + File[] filesList = corpusDir.listFiles(); + if (filesList == null) { + System.out.println("'" + args[0] + "' is not a directory"); + return; + } + for (File file : filesList) { + if (!file.isFile()) { + continue; + } + String fileName = file.getAbsolutePath(); + if (!fileName.endsWith(".brotli")) { + continue; + } + fileNames.add(fileName); + } + int fileCount = fileNames.size(); + if (fileCount == 0) { + System.out.println("No files found"); + return; + } + byte[][] files = new byte[fileCount][]; + for (int i = 0; i < fileCount; ++i) { + files[i] = readFile(fileNames.get(i)); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(65536); + byte[] buffer = new byte[65536]; + + int warmupRepeat = 10; + long bytesDecoded = 0; + for (int i = 0; i < warmupRepeat; ++i) { + bytesDecoded = 0; + for (int j = 0; j < fileCount; ++j) { + baos.reset(); + bytesDecoded += decodeBytes(new ByteArrayInputStream(files[j]), baos, buffer); + } + } + + int repeat = 100; + + long bestTime = Long.MAX_VALUE; + for (int i = 0; i < repeat; ++i) { + long start = System.nanoTime(); + for (int j = 0; j < fileCount; ++j) { + baos.reset(); + decodeBytes(new ByteArrayInputStream(files[j]), baos, buffer); + } + long end = System.nanoTime(); + long timeDelta = end - start; + if (timeDelta < bestTime) { + bestTime = timeDelta; + } + } + + double timeDeltaSeconds = bestTime / 1000000000.0; + if (timeDeltaSeconds <= 0) { + return; + } + double mbDecoded = bytesDecoded / (1024.0 * 1024.0); + System.out.println(mbDecoded / timeDeltaSeconds); + } +} diff --git a/java/org/brotli/integration/test_data.zip b/java/org/brotli/integration/test_data.zip Binary files differindex 15ed862..9cb7bff 100644 --- a/java/org/brotli/integration/test_data.zip +++ b/java/org/brotli/integration/test_data.zip diff --git a/java/org/brotli/wrapper/android/JniHelper.java b/java/org/brotli/wrapper/android/JniHelper.java new file mode 100644 index 0000000..4ec9fe5 --- /dev/null +++ b/java/org/brotli/wrapper/android/JniHelper.java @@ -0,0 +1,83 @@ +package org.brotli.wrapper.android; + +import android.content.Context; +import android.os.Build; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipFile; + +public class JniHelper { + + // Should be set on application start. + static Context context = null; + + private static final String LIB_NAME = "native"; + + private static void tryInitialize() { + try { + System.loadLibrary(LIB_NAME); + } catch (UnsatisfiedLinkError e) { + if (context == null) { + throw e; + } + int sdk = Build.VERSION.SDK_INT; + boolean tryFallback = + (sdk >= Build.VERSION_CODES.JELLY_BEAN) && (sdk <= Build.VERSION_CODES.KITKAT); + if (!tryFallback) { + throw e; + } + try { + String libraryFileName = "lib" + LIB_NAME + ".so"; + String libraryFullPath = context.getFilesDir() + File.separator + libraryFileName; + File file = new File(libraryFullPath); + if (!file.exists()) { + String apkPath = context.getApplicationInfo().sourceDir; + String pathInApk = "lib/" + Build.CPU_ABI + "/lib" + LIB_NAME + ".so"; + unzip(apkPath, pathInApk, libraryFullPath); + } + System.load(libraryFullPath); + } catch (UnsatisfiedLinkError unsatisfiedLinkError) { + throw unsatisfiedLinkError; + } catch (Throwable t) { + UnsatisfiedLinkError unsatisfiedLinkError = new UnsatisfiedLinkError( + "Exception while extracting native library: " + t.getMessage()); + unsatisfiedLinkError.initCause(t); + throw unsatisfiedLinkError; + } + } + } + + private static final Object mutex = new Object(); + private static volatile boolean alreadyInitialized; + + public static void ensureInitialized() { + synchronized (mutex) { + if (!alreadyInitialized) { + // If failed, do not retry. + alreadyInitialized = true; + tryInitialize(); + } + } + } + + private static void unzip(String zipFileName, String entryName, String outputFileName) + throws IOException { + ZipFile zipFile = new ZipFile(zipFileName); + try { + InputStream input = zipFile.getInputStream(zipFile.getEntry(entryName)); + OutputStream output = new FileOutputStream(outputFileName); + byte[] data = new byte[16384]; + int len; + while ((len = input.read(data)) != -1) { + output.write(data, 0, len); + } + output.close(); + input.close(); + } finally { + zipFile.close(); + } + } +} diff --git a/java/org/brotli/wrapper/android/UseJni.java b/java/org/brotli/wrapper/android/UseJni.java new file mode 100644 index 0000000..a10f1b0 --- /dev/null +++ b/java/org/brotli/wrapper/android/UseJni.java @@ -0,0 +1,22 @@ +package org.brotli.wrapper.android; + +import org.brotli.wrapper.dec.Decoder; +import org.brotli.wrapper.enc.Encoder; +import java.io.IOException; + +public final class UseJni { + + static { + JniHelper.ensureInitialized(); + } + + public static int deepThought() { + String theUltimateQuestion = "What do you get when you multiply six by 9"; + try { + return Decoder.decompress( + Encoder.compress(new byte[theUltimateQuestion.length()])).length; + } catch (IOException ex) { + throw new RuntimeException("Please wait 7.5 million years"); + } + } +} diff --git a/java/org/brotli/wrapper/android/UseJniTest.java b/java/org/brotli/wrapper/android/UseJniTest.java new file mode 100644 index 0000000..16782fc --- /dev/null +++ b/java/org/brotli/wrapper/android/UseJniTest.java @@ -0,0 +1,23 @@ +package org.brotli.wrapper.android; + +import static junit.framework.Assert.assertEquals; + +import androidx.test.core.app.ApplicationProvider; +import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(GoogleRobolectricTestRunner.class) +public final class UseJniTest { + + @Before + public void setup() { + JniHelper.context = ApplicationProvider.getApplicationContext(); + } + + @Test + public void testAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything() { + assertEquals(42, UseJni.deepThought()); + } +} diff --git a/java/org/brotli/wrapper/dec/BUILD b/java/org/brotli/wrapper/dec/BUILD index 6190504..7a99e98 100644 --- a/java/org/brotli/wrapper/dec/BUILD +++ b/java/org/brotli/wrapper/dec/BUILD @@ -4,7 +4,12 @@ licenses(["notice"]) # MIT filegroup( name = "jni_src", - srcs = ["decoder_jni.cc"], + srcs = [ + "decoder_jni.cc", + "decoder_jni.h", + # TODO: investigate, why this prevents JNI library loading. + #"decoder_jni_onload.cc", + ], ) java_library( diff --git a/java/org/brotli/wrapper/dec/decoder_jni.cc b/java/org/brotli/wrapper/dec/decoder_jni.cc index e10ffe3..d6e60e9 100644 --- a/java/org/brotli/wrapper/dec/decoder_jni.cc +++ b/java/org/brotli/wrapper/dec/decoder_jni.cc @@ -4,7 +4,7 @@ See file LICENSE for detail or copy at https://opensource.org/licenses/MIT */ -#include <jni.h> +#include "./decoder_jni.h" #include <new> diff --git a/java/org/brotli/wrapper/dec/decoder_jni.h b/java/org/brotli/wrapper/dec/decoder_jni.h new file mode 100644 index 0000000..6f30506 --- /dev/null +++ b/java/org/brotli/wrapper/dec/decoder_jni.h @@ -0,0 +1,75 @@ +/* Copyright 2017 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +#ifndef BROTLI_WRAPPER_DEC_DECODER_JNI_H_ +#define BROTLI_WRAPPER_DEC_DECODER_JNI_H_ + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Creates a new Decoder. + * + * Cookie to address created decoder is stored in out_cookie. In case of failure + * cookie is 0. + * + * @param ctx {out_cookie, in_directBufferSize} tuple + * @returns direct ByteBuffer if directBufferSize is not 0; otherwise null + */ +JNIEXPORT jobject JNICALL +Java_org_brotli_wrapper_dec_DecoderJNI_nativeCreate( + JNIEnv* env, jobject /*jobj*/, jlongArray ctx); + +/** + * Push data to decoder. + * + * status codes: + * - 0 error happened + * - 1 stream is finished, no more input / output expected + * - 2 needs more input to process further + * - 3 needs more output to process further + * - 4 ok, can proceed further without additional input + * + * @param ctx {in_cookie, out_status} tuple + * @param input_length number of bytes provided in input or direct input; + * 0 to process further previous input + */ +JNIEXPORT void JNICALL +Java_org_brotli_wrapper_dec_DecoderJNI_nativePush( + JNIEnv* env, jobject /*jobj*/, jlongArray ctx, jint input_length); + +/** + * Pull decompressed data from decoder. + * + * @param ctx {in_cookie, out_status} tuple + * @returns direct ByteBuffer; all the produced data MUST be consumed before + * any further invocation; null in case of error + */ +JNIEXPORT jobject JNICALL +Java_org_brotli_wrapper_dec_DecoderJNI_nativePull( + JNIEnv* env, jobject /*jobj*/, jlongArray ctx); + +/** + * Releases all used resources. + * + * @param ctx {in_cookie} tuple + */ +JNIEXPORT void JNICALL +Java_org_brotli_wrapper_dec_DecoderJNI_nativeDestroy( + JNIEnv* env, jobject /*jobj*/, jlongArray ctx); + +JNIEXPORT jboolean JNICALL +Java_org_brotli_wrapper_dec_DecoderJNI_nativeAttachDictionary( + JNIEnv* env, jobject /*jobj*/, jlongArray ctx, jobject dictionary); + +#ifdef __cplusplus +} +#endif + +#endif // BROTLI_WRAPPER_DEC_DECODER_JNI_H_ diff --git a/java/org/brotli/wrapper/dec/decoder_jni_onload.cc b/java/org/brotli/wrapper/dec/decoder_jni_onload.cc new file mode 100644 index 0000000..12f8eba --- /dev/null +++ b/java/org/brotli/wrapper/dec/decoder_jni_onload.cc @@ -0,0 +1,55 @@ +/* Copyright 2017 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +#include <jni.h> + +#include "./decoder_jni.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const JNINativeMethod kDecoderMethods[] = { + {"nativeCreate", "([J)Ljava/nio/ByteBuffer;", + reinterpret_cast<void*>( + Java_org_brotli_wrapper_dec_DecoderJNI_nativeCreate)}, + {"nativePush", "([JI)V", + reinterpret_cast<void*>( + Java_org_brotli_wrapper_dec_DecoderJNI_nativePush)}, + {"nativePull", "([J)Ljava/nio/ByteBuffer;", + reinterpret_cast<void*>( + Java_org_brotli_wrapper_dec_DecoderJNI_nativePull)}, + {"nativeDestroy", "([J)V", + reinterpret_cast<void*>( + Java_org_brotli_wrapper_dec_DecoderJNI_nativeDestroy)}, + {"nativeAttachDictionary", "([JLjava/nio/ByteBuffer;)Z", + reinterpret_cast<void*>( + Java_org_brotli_wrapper_dec_DecoderJNI_nativeAttachDictionary)}}; + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + + jclass clazz = + env->FindClass("com/google/compression/brotli/wrapper/dec/DecoderJNI"); + if (clazz == nullptr) { + return -1; + } + + if (env->RegisterNatives( + clazz, kDecoderMethods, + sizeof(kDecoderMethods) / sizeof(kDecoderMethods[0])) < 0) { + return -1; + } + + return JNI_VERSION_1_6; +} + +#ifdef __cplusplus +} +#endif diff --git a/java/org/brotli/wrapper/enc/Encoder.java b/java/org/brotli/wrapper/enc/Encoder.java index 2aa9890..88af481 100644 --- a/java/org/brotli/wrapper/enc/Encoder.java +++ b/java/org/brotli/wrapper/enc/Encoder.java @@ -69,6 +69,8 @@ public class Encoder { } /** + * Setup encoder quality. + * * @param quality compression quality, or -1 for default */ public Parameters setQuality(int quality) { @@ -80,6 +82,8 @@ public class Encoder { } /** + * Setup encoder window size. + * * @param lgwin log2(LZ window size), or -1 for default */ public Parameters setWindow(int lgwin) { @@ -91,6 +95,8 @@ public class Encoder { } /** + * Setup encoder compression mode. + * * @param mode compression mode, or {@code null} for default */ public Parameters setMode(Mode mode) { @@ -116,7 +122,8 @@ public class Encoder { } this.dictionaries = new ArrayList<PreparedDictionary>(); this.destination = destination; - this.encoder = new EncoderJNI.Wrapper(inputBufferSize, params.quality, params.lgwin, params.mode); + this.encoder = + new EncoderJNI.Wrapper(inputBufferSize, params.quality, params.lgwin, params.mode); this.inputBuffer = this.encoder.getInputBuffer(); } @@ -212,7 +219,8 @@ public class Encoder { return empty; } /* data.length > 0 */ - EncoderJNI.Wrapper encoder = new EncoderJNI.Wrapper(data.length, params.quality, params.lgwin, params.mode); + EncoderJNI.Wrapper encoder = + new EncoderJNI.Wrapper(data.length, params.quality, params.lgwin, params.mode); ArrayList<byte[]> output = new ArrayList<byte[]>(); int totalOutputSize = 0; try { |