Edit

kc3-lang/brotli/java/dec/BrotliInputStream.java

Branch :

  • Show log

    Commit

  • Author : Eugene Kliuchnikov
    Date : 2016-10-17 14:04:59
    Hash : 5025365d
    Message : Add Java port of Brotli decoder.

  • java/dec/BrotliInputStream.java
  • /* 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 = 16384;
    
      /**
       * 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 kilobyte of data of source is available.
       *
       * @param source underlying data source
       */
      public BrotliInputStream(InputStream source) throws IOException {
        this(source, DEFAULT_INTERNAL_BUFFER_SIZE, null);
      }
    
      /**
       * 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 kilobyte of data of source is available.
       *
       * @param source compressed data source
       * @param byteReadBufferSize size of internal buffer used in case of
       *        byte-by-byte reading
       */
      public BrotliInputStream(InputStream source, int byteReadBufferSize) throws IOException {
        this(source, byteReadBufferSize, null);
      }
    
      /**
       * 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 kilobyte of data of source is available.
       *
       * @param source compressed data source
       * @param byteReadBufferSize size of internal buffer used in case of
       *        byte-by-byte reading
       * @param customDictionary custom dictionary data; {@code null} if not used
       */
      public BrotliInputStream(InputStream source, int byteReadBufferSize,
          byte[] customDictionary) 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 {
          State.setInput(state, source);
        } catch (BrotliRuntimeException ex) {
          throw new IOException(ex);
        }
        if (customDictionary != null) {
          Decode.setCustomDictionary(state, customDictionary);
        }
      }
    
      /**
       * {@inheritDoc}
       */
      @Override
      public int read() throws IOException {
        try {
          if (bufferOffset >= remainingBufferBytes) {
            remainingBufferBytes = read(buffer, 0, buffer.length);
            bufferOffset = 0;
            if (remainingBufferBytes == -1) {
              return -1;
            }
          }
          return buffer[bufferOffset++] & 0xFF;
        } catch (BrotliRuntimeException ex) {
          throw new IOException(ex);
        }
      }
    
      /**
       * {@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(ex);
        }
      }
    }