aboutsummaryrefslogtreecommitdiff
path: root/java/org/brotli/wrapper/dec/DecoderJNI.java
blob: 7b8dacef1e7071212269dadd5ca9586a04bbe4f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* Copyright 2017 Google Inc. All Rights Reserved.

   Distributed under MIT license.
   See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/

package org.brotli.wrapper.dec;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * JNI wrapper for brotli decoder.
 */
public class DecoderJNI {
  private static native ByteBuffer nativeCreate(long[] context);
  private static native void nativePush(long[] context, int length);
  private static native ByteBuffer nativePull(long[] context);
  private static native void nativeDestroy(long[] context);
  private static native boolean nativeAttachDictionary(long[] context, ByteBuffer dictionary);

  public enum Status {
    ERROR,
    DONE,
    NEEDS_MORE_INPUT,
    NEEDS_MORE_OUTPUT,
    OK
  };

  public static class Wrapper {
    private final long[] context = new long[3];
    private final ByteBuffer inputBuffer;
    private Status lastStatus = Status.NEEDS_MORE_INPUT;
    private boolean fresh = true;

    public Wrapper(int inputBufferSize) throws IOException {
      this.context[1] = inputBufferSize;
      this.inputBuffer = nativeCreate(this.context);
      if (this.context[0] == 0) {
        throw new IOException("failed to initialize native brotli decoder");
      }
    }

    public boolean attachDictionary(ByteBuffer dictionary) {
      if (!dictionary.isDirect()) {
        throw new IllegalArgumentException("only direct buffers allowed");
      }
      if (context[0] == 0) {
        throw new IllegalStateException("brotli decoder is already destroyed");
      }
      if (!fresh) {
        throw new IllegalStateException("decoding is already started");
      }
      return nativeAttachDictionary(context, dictionary);
    }

    public void push(int length) {
      if (length < 0) {
        throw new IllegalArgumentException("negative block length");
      }
      if (context[0] == 0) {
        throw new IllegalStateException("brotli decoder is already destroyed");
      }
      if (lastStatus != Status.NEEDS_MORE_INPUT && lastStatus != Status.OK) {
        throw new IllegalStateException("pushing input to decoder in " + lastStatus + " state");
      }
      if (lastStatus == Status.OK && length != 0) {
        throw new IllegalStateException("pushing input to decoder in OK state");
      }
      fresh = false;
      nativePush(context, length);
      parseStatus();
    }

    private void parseStatus() {
      long status = context[1];
      if (status == 1) {
        lastStatus = Status.DONE;
      } else if (status == 2) {
        lastStatus = Status.NEEDS_MORE_INPUT;
      } else if (status == 3) {
        lastStatus = Status.NEEDS_MORE_OUTPUT;
      } else if (status == 4) {
        lastStatus = Status.OK;
      } else {
        lastStatus = Status.ERROR;
      }
    }

    public Status getStatus() {
      return lastStatus;
    }

    public ByteBuffer getInputBuffer() {
      return inputBuffer;
    }

    public boolean hasOutput() {
      return context[2] != 0;
    }

    public ByteBuffer pull() {
      if (context[0] == 0) {
        throw new IllegalStateException("brotli decoder is already destroyed");
      }
      if (lastStatus != Status.NEEDS_MORE_OUTPUT && !hasOutput()) {
        throw new IllegalStateException("pulling output from decoder in " + lastStatus + " state");
      }
      fresh = false;
      ByteBuffer result = nativePull(context);
      parseStatus();
      return result;
    }

    /**
     * Releases native resources.
     */
    public void destroy() {
      if (context[0] == 0) {
        throw new IllegalStateException("brotli decoder is already destroyed");
      }
      nativeDestroy(context);
      context[0] = 0;
    }

    @Override
    protected void finalize() throws Throwable {
      if (context[0] != 0) {
        /* TODO(eustas): log resource leak? */
        destroy();
      }
      super.finalize();
    }
  }
}