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