aboutsummaryrefslogtreecommitdiff
path: root/java/org/brotli/wrapper/enc/Encoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/org/brotli/wrapper/enc/Encoder.java')
-rwxr-xr-xjava/org/brotli/wrapper/enc/Encoder.java200
1 files changed, 200 insertions, 0 deletions
diff --git a/java/org/brotli/wrapper/enc/Encoder.java b/java/org/brotli/wrapper/enc/Encoder.java
new file mode 100755
index 0000000..55cc369
--- /dev/null
+++ b/java/org/brotli/wrapper/enc/Encoder.java
@@ -0,0 +1,200 @@
+/* Copyright 2017 Google Inc. All Rights Reserved.
+
+ Distributed under MIT license.
+ See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
+*/
+
+package org.brotli.wrapper.enc;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.util.ArrayList;
+
+/**
+ * Base class for OutputStream / Channel implementations.
+ */
+public class Encoder {
+ private final WritableByteChannel destination;
+ private final EncoderJNI.Wrapper encoder;
+ final ByteBuffer inputBuffer;
+ ByteBuffer buffer;
+ boolean closed;
+
+ /**
+ * Brotli encoder settings.
+ */
+ public static final class Parameters {
+ private int quality = -1;
+ private int lgwin = -1;
+
+ public Parameters() { }
+
+ private Parameters(Parameters other) {
+ this.quality = other.quality;
+ this.lgwin = other.lgwin;
+ }
+
+ /**
+ * @param quality compression quality, or -1 for default
+ */
+ public Parameters setQuality(int quality) {
+ if (quality < -1 || quality > 11) {
+ throw new IllegalArgumentException("quality should be in range [0, 11], or -1");
+ }
+ this.quality = quality;
+ return this;
+ }
+
+ /**
+ * @param lgwin log2(LZ window size), or -1 for default
+ */
+ public Parameters setWindow(int lgwin) {
+ if ((lgwin != -1) && ((lgwin < 10) || (lgwin > 24))) {
+ throw new IllegalArgumentException("lgwin should be in range [10, 24], or -1");
+ }
+ this.lgwin = lgwin;
+ return this;
+ }
+ }
+
+ /**
+ * Creates a Encoder wrapper.
+ *
+ * @param destination underlying destination
+ * @param params encoding parameters
+ * @param inputBufferSize read buffer size
+ */
+ Encoder(WritableByteChannel destination, Parameters params, int inputBufferSize,
+ ByteBuffer customDictionary) throws IOException {
+ if (inputBufferSize <= 0) {
+ throw new IllegalArgumentException("buffer size must be positive");
+ }
+ if (destination == null) {
+ throw new NullPointerException("destination can not be null");
+ }
+ this.destination = destination;
+ this.encoder = new EncoderJNI.Wrapper(
+ inputBufferSize, params.quality, params.lgwin, customDictionary);
+ this.inputBuffer = this.encoder.getInputBuffer();
+ }
+
+ private void fail(String message) throws IOException {
+ try {
+ close();
+ } catch (IOException ex) {
+ /* Ignore */
+ }
+ throw new IOException(message);
+ }
+
+ /**
+ * @param force repeat pushing until all output is consumed
+ * @return true if all encoder output is consumed
+ */
+ boolean pushOutput(boolean force) throws IOException {
+ while (buffer != null) {
+ if (buffer.hasRemaining()) {
+ destination.write(buffer);
+ }
+ if (!buffer.hasRemaining()) {
+ buffer = null;
+ } else if (!force) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return true if there is space in inputBuffer.
+ */
+ boolean encode(EncoderJNI.Operation op) throws IOException {
+ boolean force = (op != EncoderJNI.Operation.PROCESS);
+ if (force) {
+ inputBuffer.limit(inputBuffer.position());
+ } else if (inputBuffer.hasRemaining()) {
+ return true;
+ }
+ boolean hasInput = true;
+ while (true) {
+ if (!encoder.isSuccess()) {
+ fail("encoding failed");
+ } else if (!pushOutput(force)) {
+ return false;
+ } else if (encoder.hasMoreOutput()) {
+ buffer = encoder.pull();
+ } else if (encoder.hasRemainingInput()) {
+ encoder.push(op, 0);
+ } else if (hasInput) {
+ encoder.push(op, inputBuffer.limit());
+ hasInput = false;
+ } else {
+ inputBuffer.clear();
+ return true;
+ }
+ }
+ }
+
+ void flush() throws IOException {
+ encode(EncoderJNI.Operation.FLUSH);
+ }
+
+ void close() throws IOException {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ try {
+ encode(EncoderJNI.Operation.FINISH);
+ } finally {
+ encoder.destroy();
+ destination.close();
+ }
+ }
+
+ /**
+ * Encodes the given data buffer.
+ */
+ public static byte[] compress(byte[] data, Parameters params) throws IOException {
+ EncoderJNI.Wrapper encoder = new EncoderJNI.Wrapper(
+ data.length, params.quality, params.lgwin, null);
+ ArrayList<byte[]> output = new ArrayList<byte[]>();
+ int totalOutputSize = 0;
+ try {
+ encoder.getInputBuffer().put(data);
+ encoder.push(EncoderJNI.Operation.FINISH, data.length);
+ while (true) {
+ if (!encoder.isSuccess()) {
+ throw new IOException("encoding failed");
+ } else if (encoder.hasMoreOutput()) {
+ ByteBuffer buffer = encoder.pull();
+ byte[] chunk = new byte[buffer.remaining()];
+ buffer.get(chunk);
+ output.add(chunk);
+ totalOutputSize += chunk.length;
+ } else if (encoder.hasRemainingInput()) {
+ encoder.push(EncoderJNI.Operation.FINISH, 0);
+ } else {
+ break;
+ }
+ }
+ } finally {
+ encoder.destroy();
+ }
+ if (output.size() == 1) {
+ return output.get(0);
+ }
+ byte[] result = new byte[totalOutputSize];
+ int offset = 0;
+ for (byte[] chunk : output) {
+ System.arraycopy(chunk, 0, result, offset, chunk.length);
+ offset += chunk.length;
+ }
+ return result;
+ }
+
+ public static byte[] compress(byte[] data) throws IOException {
+ return compress(data, new Parameters());
+ }
+}