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.enc; 8 9 import java.io.IOException; 10 import java.nio.ByteBuffer; 11 12 /** 13 * JNI wrapper for brotli encoder. 14 */ 15 class EncoderJNI { 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 enum Operation { 22 PROCESS, 23 FLUSH, 24 FINISH 25 } 26 27 static class Wrapper { 28 protected final long[] context = new long[5]; 29 private final ByteBuffer inputBuffer; 30 private boolean fresh = true; 31 Wrapper(int inputBufferSize, int quality, int lgwin)32 Wrapper(int inputBufferSize, int quality, int lgwin) 33 throws IOException { 34 if (inputBufferSize <= 0) { 35 throw new IOException("buffer size must be positive"); 36 } 37 this.context[1] = inputBufferSize; 38 this.context[2] = quality; 39 this.context[3] = lgwin; 40 this.inputBuffer = nativeCreate(this.context); 41 if (this.context[0] == 0) { 42 throw new IOException("failed to initialize native brotli encoder"); 43 } 44 this.context[1] = 1; 45 this.context[2] = 0; 46 this.context[3] = 0; 47 } 48 push(Operation op, int length)49 void push(Operation op, int length) { 50 if (length < 0) { 51 throw new IllegalArgumentException("negative block length"); 52 } 53 if (context[0] == 0) { 54 throw new IllegalStateException("brotli encoder is already destroyed"); 55 } 56 if (!isSuccess() || hasMoreOutput()) { 57 throw new IllegalStateException("pushing input to encoder in unexpected state"); 58 } 59 if (hasRemainingInput() && length != 0) { 60 throw new IllegalStateException("pushing input to encoder over previous input"); 61 } 62 context[1] = op.ordinal(); 63 fresh = false; 64 nativePush(context, length); 65 } 66 isSuccess()67 boolean isSuccess() { 68 return context[1] != 0; 69 } 70 hasMoreOutput()71 boolean hasMoreOutput() { 72 return context[2] != 0; 73 } 74 hasRemainingInput()75 boolean hasRemainingInput() { 76 return context[3] != 0; 77 } 78 isFinished()79 boolean isFinished() { 80 return context[4] != 0; 81 } 82 getInputBuffer()83 ByteBuffer getInputBuffer() { 84 return inputBuffer; 85 } 86 pull()87 ByteBuffer pull() { 88 if (context[0] == 0) { 89 throw new IllegalStateException("brotli encoder is already destroyed"); 90 } 91 if (!isSuccess() || !hasMoreOutput()) { 92 throw new IllegalStateException("pulling while data is not ready"); 93 } 94 fresh = false; 95 return nativePull(context); 96 } 97 98 /** 99 * Releases native resources. 100 */ destroy()101 void destroy() { 102 if (context[0] == 0) { 103 throw new IllegalStateException("brotli encoder 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