aboutsummaryrefslogtreecommitdiff
path: root/java/org/brotli/dec/BrotliInputStream.java
blob: b99e40a2635bc7402f22c9a971665129b312ba7f (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* Copyright 2015 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.dec;

import java.io.IOException;
import java.io.InputStream;

/**
 * {@link InputStream} decorator that decompresses brotli data.
 *
 * <p> Not thread-safe.
 */
public class BrotliInputStream extends InputStream {

  public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 256;

  /**
   * Internal buffer used for efficient byte-by-byte reading.
   */
  private byte[] buffer;

  /**
   * Number of decoded but still unused bytes in internal buffer.
   */
  private int remainingBufferBytes;

  /**
   * Next unused byte offset.
   */
  private int bufferOffset;

  /**
   * Decoder state.
   */
  private final State state = new State();

  /**
   * Creates a {@link InputStream} wrapper that decompresses brotli data.
   *
   * <p> For byte-by-byte reading ({@link #read()}) internal buffer with
   * {@link #DEFAULT_INTERNAL_BUFFER_SIZE} size is allocated and used.
   *
   * <p> Will block the thread until first {@link BitReader#CAPACITY} bytes of data of source
   * are available.
   *
   * @param source underlying data source
   * @throws IOException in case of corrupted data or source stream problems
   */
  public BrotliInputStream(InputStream source) throws IOException {
    this(source, DEFAULT_INTERNAL_BUFFER_SIZE);
  }

  /**
   * Creates a {@link InputStream} wrapper that decompresses brotli data.
   *
   * <p> For byte-by-byte reading ({@link #read()}) internal buffer of specified size is
   * allocated and used.
   *
   * <p> Will block the thread until first {@link BitReader#CAPACITY} bytes of data of source
   * are available.
   *
   * @param source compressed data source
   * @param byteReadBufferSize size of internal buffer used in case of
   *        byte-by-byte reading
   * @throws IOException in case of corrupted data or source stream problems
   */
  public BrotliInputStream(InputStream source, int byteReadBufferSize) throws IOException {
    if (byteReadBufferSize <= 0) {
      throw new IllegalArgumentException("Bad buffer size:" + byteReadBufferSize);
    } else if (source == null) {
      throw new IllegalArgumentException("source is null");
    }
    this.buffer = new byte[byteReadBufferSize];
    this.remainingBufferBytes = 0;
    this.bufferOffset = 0;
    try {
      Decode.initState(state, source);
    } catch (BrotliRuntimeException ex) {
      throw new IOException("Brotli decoder initialization failed", ex);
    }
  }

  public void enableEagerOutput() {
    Decode.enableEagerOutput(state);
  }

  public void enableLargeWindow() {
    Decode.enableLargeWindow(state);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void close() throws IOException {
    Decode.close(state);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int read() throws IOException {
    if (bufferOffset >= remainingBufferBytes) {
      remainingBufferBytes = read(buffer, 0, buffer.length);
      bufferOffset = 0;
      if (remainingBufferBytes == -1) {
        return -1;
      }
    }
    return buffer[bufferOffset++] & 0xFF;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int read(byte[] destBuffer, int destOffset, int destLen) throws IOException {
    if (destOffset < 0) {
      throw new IllegalArgumentException("Bad offset: " + destOffset);
    } else if (destLen < 0) {
      throw new IllegalArgumentException("Bad length: " + destLen);
    } else if (destOffset + destLen > destBuffer.length) {
      throw new IllegalArgumentException(
          "Buffer overflow: " + (destOffset + destLen) + " > " + destBuffer.length);
    } else if (destLen == 0) {
      return 0;
    }
    int copyLen = Math.max(remainingBufferBytes - bufferOffset, 0);
    if (copyLen != 0) {
      copyLen = Math.min(copyLen, destLen);
      System.arraycopy(buffer, bufferOffset, destBuffer, destOffset, copyLen);
      bufferOffset += copyLen;
      destOffset += copyLen;
      destLen -= copyLen;
      if (destLen == 0) {
        return copyLen;
      }
    }
    try {
      state.output = destBuffer;
      state.outputOffset = destOffset;
      state.outputLength = destLen;
      state.outputUsed = 0;
      Decode.decompress(state);
      if (state.outputUsed == 0) {
        return -1;
      }
      return state.outputUsed + copyLen;
    } catch (BrotliRuntimeException ex) {
      throw new IOException("Brotli stream decoding failed", ex);
    }

    // <{[INJECTED CODE]}>
  }
}