aboutsummaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
authorEugene Kliuchnikov <eustas.ru@gmail.com>2021-08-18 19:15:07 +0200
committerGitHub <noreply@github.com>2021-08-18 19:15:07 +0200
commit68f1b90ad0d204907beb58304d0bd06391001a4d (patch)
tree15fcfe4d6dd5a3058f49e8a6c680afb2ec4c97d0 /java
parent19d86fb9a60aa7034d4981b69a5b656f5b90017e (diff)
downloadbrotli-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/BUILD9
-rw-r--r--java/org/brotli/dec/Decoder.java61
-rw-r--r--java/org/brotli/integration/Benchmark.java117
-rw-r--r--java/org/brotli/integration/test_data.zipbin2859607 -> 2857520 bytes
-rw-r--r--java/org/brotli/wrapper/android/JniHelper.java83
-rw-r--r--java/org/brotli/wrapper/android/UseJni.java22
-rw-r--r--java/org/brotli/wrapper/android/UseJniTest.java23
-rw-r--r--java/org/brotli/wrapper/dec/BUILD7
-rw-r--r--java/org/brotli/wrapper/dec/decoder_jni.cc2
-rw-r--r--java/org/brotli/wrapper/dec/decoder_jni.h75
-rw-r--r--java/org/brotli/wrapper/dec/decoder_jni_onload.cc55
-rw-r--r--java/org/brotli/wrapper/enc/Encoder.java12
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
index 15ed862..9cb7bff 100644
--- a/java/org/brotli/integration/test_data.zip
+++ b/java/org/brotli/integration/test_data.zip
Binary files differ
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 {