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 7 package org.brotli.wrapper.dec; 8 9 import java.io.IOException; 10 import java.nio.ByteBuffer; 11 12 /** 13 * JNI wrapper for brotli decoder. 14 */ 15 public class DecoderJNI { nativeCreate(long[] context)16 private static native ByteBuffer nativeCreate(long[] context); nativePush(long[] context, int length)17 private static native void nativePush(long[] context, int length); nativePull(long[] context)18 private static native ByteBuffer nativePull(long[] context); nativeDestroy(long[] context)19 private static native void nativeDestroy(long[] context); 20 21 public enum Status { 22 ERROR, 23 DONE, 24 NEEDS_MORE_INPUT, 25 NEEDS_MORE_OUTPUT, 26 OK 27 }; 28 29 public static class Wrapper { 30 private final long[] context = new long[3]; 31 private final ByteBuffer inputBuffer; 32 private Status lastStatus = Status.NEEDS_MORE_INPUT; 33 Wrapper(int inputBufferSize)34 public 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 push(int length)42 public 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 parseStatus()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 getStatus()74 public Status getStatus() { 75 return lastStatus; 76 } 77 getInputBuffer()78 public ByteBuffer getInputBuffer() { 79 return inputBuffer; 80 } 81 hasOutput()82 public boolean hasOutput() { 83 return context[2] != 0; 84 } 85 pull()86 public ByteBuffer pull() { 87 if (context[0] == 0) { 88 throw new IllegalStateException("brotli decoder is already destroyed"); 89 } 90 if (lastStatus != Status.NEEDS_MORE_OUTPUT && !hasOutput()) { 91 throw new IllegalStateException("pulling output from decoder in " + lastStatus + " state"); 92 } 93 ByteBuffer result = nativePull(context); 94 parseStatus(); 95 return result; 96 } 97 98 /** 99 * Releases native resources. 100 */ destroy()101 public void destroy() { 102 if (context[0] == 0) { 103 throw new IllegalStateException("brotli decoder is already destroyed"); 104 } 105 nativeDestroy(context); 106 context[0] = 0; 107 } 108 109 @Override finalize()110 protected void finalize() throws Throwable { 111 if (context[0] != 0) { 112 /* TODO: log resource leak? */ 113 destroy(); 114 } 115 super.finalize(); 116 } 117 } 118 } 119