/* 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.dec;
|
|
import java.io.IOException;
|
import java.nio.ByteBuffer;
|
|
/**
|
* JNI wrapper for brotli decoder.
|
*/
|
public class DecoderJNI {
|
private static native ByteBuffer nativeCreate(long[] context);
|
private static native void nativePush(long[] context, int length);
|
private static native ByteBuffer nativePull(long[] context);
|
private static native void nativeDestroy(long[] context);
|
|
public enum Status {
|
ERROR,
|
DONE,
|
NEEDS_MORE_INPUT,
|
NEEDS_MORE_OUTPUT,
|
OK
|
};
|
|
public static class Wrapper {
|
private final long[] context = new long[3];
|
private final ByteBuffer inputBuffer;
|
private Status lastStatus = Status.NEEDS_MORE_INPUT;
|
private boolean fresh = true;
|
|
public Wrapper(int inputBufferSize) throws IOException {
|
this.context[1] = inputBufferSize;
|
this.inputBuffer = nativeCreate(this.context);
|
if (this.context[0] == 0) {
|
throw new IOException("failed to initialize native brotli decoder");
|
}
|
}
|
|
public void push(int length) {
|
if (length < 0) {
|
throw new IllegalArgumentException("negative block length");
|
}
|
if (context[0] == 0) {
|
throw new IllegalStateException("brotli decoder is already destroyed");
|
}
|
if (lastStatus != Status.NEEDS_MORE_INPUT && lastStatus != Status.OK) {
|
throw new IllegalStateException("pushing input to decoder in " + lastStatus + " state");
|
}
|
if (lastStatus == Status.OK && length != 0) {
|
throw new IllegalStateException("pushing input to decoder in OK state");
|
}
|
fresh = false;
|
nativePush(context, length);
|
parseStatus();
|
}
|
|
private void parseStatus() {
|
long status = context[1];
|
if (status == 1) {
|
lastStatus = Status.DONE;
|
} else if (status == 2) {
|
lastStatus = Status.NEEDS_MORE_INPUT;
|
} else if (status == 3) {
|
lastStatus = Status.NEEDS_MORE_OUTPUT;
|
} else if (status == 4) {
|
lastStatus = Status.OK;
|
} else {
|
lastStatus = Status.ERROR;
|
}
|
}
|
|
public Status getStatus() {
|
return lastStatus;
|
}
|
|
public ByteBuffer getInputBuffer() {
|
return inputBuffer;
|
}
|
|
public boolean hasOutput() {
|
return context[2] != 0;
|
}
|
|
public ByteBuffer pull() {
|
if (context[0] == 0) {
|
throw new IllegalStateException("brotli decoder is already destroyed");
|
}
|
if (lastStatus != Status.NEEDS_MORE_OUTPUT && !hasOutput()) {
|
throw new IllegalStateException("pulling output from decoder in " + lastStatus + " state");
|
}
|
fresh = false;
|
ByteBuffer result = nativePull(context);
|
parseStatus();
|
return result;
|
}
|
|
/**
|
* Releases native resources.
|
*/
|
public void destroy() {
|
if (context[0] == 0) {
|
throw new IllegalStateException("brotli decoder is already destroyed");
|
}
|
nativeDestroy(context);
|
context[0] = 0;
|
}
|
|
@Override
|
protected void finalize() throws Throwable {
|
if (context[0] != 0) {
|
/* TODO: log resource leak? */
|
destroy();
|
}
|
super.finalize();
|
}
|
}
|
}
|