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 private boolean fresh = true; 34 Wrapper(int inputBufferSize)35 public Wrapper(int inputBufferSize) throws IOException { 36 this.context[1] = inputBufferSize; 37 this.inputBuffer = nativeCreate(this.context); 38 if (this.context[0] == 0) { 39 throw new IOException("failed to initialize native brotli decoder"); 40 } 41 } 42 push(int length)43 public void push(int length) { 44 if (length < 0) { 45 throw new IllegalArgumentException("negative block length"); 46 } 47 if (context[0] == 0) { 48 throw new IllegalStateException("brotli decoder is already destroyed"); 49 } 50 if (lastStatus != Status.NEEDS_MORE_INPUT && lastStatus != Status.OK) { 51 throw new IllegalStateException("pushing input to decoder in " + lastStatus + " state"); 52 } 53 if (lastStatus == Status.OK && length != 0) { 54 throw new IllegalStateException("pushing input to decoder in OK state"); 55 } 56 fresh = false; 57 nativePush(context, length); 58 parseStatus(); 59 } 60 parseStatus()61 private void parseStatus() { 62 long status = context[1]; 63 if (status == 1) { 64 lastStatus = Status.DONE; 65 } else if (status == 2) { 66 lastStatus = Status.NEEDS_MORE_INPUT; 67 } else if (status == 3) { 68 lastStatus = Status.NEEDS_MORE_OUTPUT; 69 } else if (status == 4) { 70 lastStatus = Status.OK; 71 } else { 72 lastStatus = Status.ERROR; 73 } 74 } 75 getStatus()76 public Status getStatus() { 77 return lastStatus; 78 } 79 getInputBuffer()80 public ByteBuffer getInputBuffer() { 81 return inputBuffer; 82 } 83 hasOutput()84 public boolean hasOutput() { 85 return context[2] != 0; 86 } 87 pull()88 public ByteBuffer pull() { 89 if (context[0] == 0) { 90 throw new IllegalStateException("brotli decoder is already destroyed"); 91 } 92 if (lastStatus != Status.NEEDS_MORE_OUTPUT && !hasOutput()) { 93 throw new IllegalStateException("pulling output from decoder in " + lastStatus + " state"); 94 } 95 fresh = false; 96 ByteBuffer result = nativePull(context); 97 parseStatus(); 98 return result; 99 } 100 101 /** 102 * Releases native resources. 103 */ destroy()104 public void destroy() { 105 if (context[0] == 0) { 106 throw new IllegalStateException("brotli decoder is already destroyed"); 107 } 108 nativeDestroy(context); 109 context[0] = 0; 110 } 111 112 @Override finalize()113 protected void finalize() throws Throwable { 114 if (context[0] != 0) { 115 /* TODO: log resource leak? */ 116 destroy(); 117 } 118 super.finalize(); 119 } 120 } 121 } 122