1/* Copyright 2017 Google Inc. All Rights Reserved. 2 3 Distributed under MIT license. 4 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5*/ 6 7package org.brotli.wrapper.dec; 8 9import java.io.IOException; 10import java.nio.ByteBuffer; 11 12/** 13 * JNI wrapper for brotli decoder. 14 */ 15class DecoderJNI { 16 private static native ByteBuffer nativeCreate(long[] context); 17 private static native void nativePush(long[] context, int length); 18 private static native ByteBuffer nativePull(long[] context); 19 private static native void nativeDestroy(long[] context); 20 21 enum Status { 22 ERROR, 23 DONE, 24 NEEDS_MORE_INPUT, 25 NEEDS_MORE_OUTPUT, 26 OK 27 }; 28 29 static class Wrapper { 30 private final long[] context = new long[2]; 31 private final ByteBuffer inputBuffer; 32 private Status lastStatus = Status.NEEDS_MORE_INPUT; 33 34 Wrapper(int inputBufferSize) throws IOException { 35 this.context[1] = inputBufferSize; 36 this.inputBuffer = nativeCreate(this.context); 37 if (this.context[0] == 0) { 38 throw new IOException("failed to initialize native brotli decoder"); 39 } 40 } 41 42 void push(int length) { 43 if (length < 0) { 44 throw new IllegalArgumentException("negative block length"); 45 } 46 if (context[0] == 0) { 47 throw new IllegalStateException("brotli decoder is already destroyed"); 48 } 49 if (lastStatus != Status.NEEDS_MORE_INPUT && lastStatus != Status.OK) { 50 throw new IllegalStateException("pushing input to decoder in " + lastStatus + " state"); 51 } 52 if (lastStatus == Status.OK && length != 0) { 53 throw new IllegalStateException("pushing input to decoder in OK state"); 54 } 55 nativePush(context, length); 56 parseStatus(); 57 } 58 59 private void parseStatus() { 60 long status = context[1]; 61 if (status == 1) { 62 lastStatus = Status.DONE; 63 } else if (status == 2) { 64 lastStatus = Status.NEEDS_MORE_INPUT; 65 } else if (status == 3) { 66 lastStatus = Status.NEEDS_MORE_OUTPUT; 67 } else if (status == 4) { 68 lastStatus = Status.OK; 69 } else { 70 lastStatus = Status.ERROR; 71 } 72 } 73 74 Status getStatus() { 75 return lastStatus; 76 } 77 78 ByteBuffer getInputBuffer() { 79 return inputBuffer; 80 } 81 82 ByteBuffer pull() { 83 if (context[0] == 0) { 84 throw new IllegalStateException("brotli decoder is already destroyed"); 85 } 86 if (lastStatus != Status.NEEDS_MORE_OUTPUT) { 87 throw new IllegalStateException("pulling output from decoder in " + lastStatus + " state"); 88 } 89 ByteBuffer result = nativePull(context); 90 parseStatus(); 91 return result; 92 } 93 94 /** 95 * Releases native resources. 96 */ 97 void destroy() { 98 if (context[0] == 0) { 99 throw new IllegalStateException("brotli decoder is already destroyed"); 100 } 101 nativeDestroy(context); 102 context[0] = 0; 103 } 104 105 @Override 106 protected void finalize() throws Throwable { 107 if (context[0] != 0) { 108 /* TODO: log resource leak? */ 109 destroy(); 110 } 111 super.finalize(); 112 } 113 } 114} 115