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