content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.cassandra.io.util;
  
  import java.io.Closeable;
  import java.io.DataInputStream;
  import java.io.EOFException;
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  
  import com.google.common.base.Preconditions;
  
  import net.nicoulaj.compilecommand.annotations.DontInline;
  import org.apache.cassandra.io.util.DataInputPlus.DataInputStreamPlus;
  import org.apache.cassandra.utils.FastByteOperations;
  import org.apache.cassandra.utils.vint.VIntCoding;
  
  import static java.lang.Math.min;
  
  /**
   * Rough equivalent of BufferedInputStream and DataInputStream wrapping a ByteBuffer that can be refilled
   * via rebuffer. Implementations provide this buffer from various channels (socket, file, memory, etc).
   * <p>
   * RebufferingInputStream is not thread safe.
   */
  public abstract class RebufferingInputStream extends DataInputStreamPlus implements DataInputPlus, Closeable
  {
      protected ByteBuffer buffer;
  
      protected RebufferingInputStream(ByteBuffer buffer)
      {
          Preconditions.checkArgument(buffer == null || buffer.order() == ByteOrder.BIG_ENDIAN, "Buffer must have BIG ENDIAN byte ordering");
          this.buffer = buffer;
      }
  
      /**
       * Implementations must implement this method to refill the buffer.
       * They can expect the buffer to be empty when this method is invoked.
       * @throws IOException
       */
      protected abstract void reBuffer() throws IOException;
  
      @Override
      public void readFully(byte[] b) throws IOException
      {
          readFully(b, 0, b.length);
      }
  
      @Override
      public void readFully(byte[] b, int off, int len) throws IOException
      {
          int read = read(b, off, len);
          if (read < len)
              throw new EOFException("EOF after " + read + " bytes out of " + len);
      }
  
      @Override
      public int read(byte[] b, int off, int len) throws IOException
      {
          // avoid int overflow
          if (off < 0 || off > b.length || len < 0 || len > b.length - off)
              throw new IndexOutOfBoundsException();
  
          if (len == 0)
              return 0;
  
          int copied = 0;
          while (copied < len)
          {
              int position = buffer.position();
              int remaining = buffer.limit() - position;
              if (remaining == 0)
              {
                  reBuffer();
                  position = buffer.position();
                  remaining = buffer.limit() - position;
                  if (remaining == 0)
                      return copied == 0 ? -1 : copied;
              }
              int toCopy = min(len - copied, remaining);
              FastByteOperations.copy(buffer, position, b, off + copied, toCopy);
              buffer.position(position + toCopy);
              copied += toCopy;
          }
  
          return copied;
      }
  
      /**
       * Equivalent to {@link #read(byte[], int, int)}, where offset is {@code dst.position()} and length is {@code dst.remaining()}
       */
      public void readFully(ByteBuffer dst) throws IOException
      {
          int offset = dst.position();
          int len = dst.limit() - offset;
  
          int copied = 0;
          while (copied < len)
          {
              int position = buffer.position();
              int remaining = buffer.limit() - position;
  
              if (remaining == 0)
              {
                  reBuffer();
  
                  position = buffer.position();
                  remaining = buffer.limit() - position;
  
                  if (remaining == 0)
                      throw new EOFException("EOF after " + copied + " bytes out of " + len);
              }
  
              int toCopy = min(len - copied, remaining);
              FastByteOperations.copy(buffer, position, dst, offset + copied, toCopy);
              buffer.position(position + toCopy);
              copied += toCopy;
          }
      }
  
      @DontInline
      protected long readPrimitiveSlowly(int bytes) throws IOException
      {
          long result = 0;
          for (int i = 0; i < bytes; i++)
              result = (result << 8) | (readByte() & 0xFFL);
          return result;
      }
  
      @Override
      public int skipBytes(int n) throws IOException
      {
          if (n <= 0)
              return 0;
          int requested = n;
          int position = buffer.position(), limit = buffer.limit(), remaining;
          while ((remaining = limit - position) < n)
          {
              n -= remaining;
              buffer.position(limit);
              reBuffer();
              position = buffer.position();
              limit = buffer.limit();
              if (position == limit)
                  return requested - n;
          }
          buffer.position(position + n);
          return requested;
      }
  
      @Override
      public boolean readBoolean() throws IOException
      {
          return readByte() != 0;
      }
  
      @Override
      public byte readByte() throws IOException
      {
          if (!buffer.hasRemaining())
          {
              reBuffer();
              if (!buffer.hasRemaining())
                  throw new EOFException();
          }
  
          return buffer.get();
      }
  
      @Override
      public int readUnsignedByte() throws IOException
      {
          return readByte() & 0xff;
      }
  
      @Override
      public short readShort() throws IOException
      {
          if (buffer.remaining() >= 2)
              return buffer.getShort();
          else
              return (short) readPrimitiveSlowly(2);
      }
  
      @Override
      public int readUnsignedShort() throws IOException
      {
          return readShort() & 0xFFFF;
      }
  
      @Override
      public char readChar() throws IOException
      {
          if (buffer.remaining() >= 2)
              return buffer.getChar();
          else
              return (char) readPrimitiveSlowly(2);
      }
  
      @Override
      public int readInt() throws IOException
      {
          if (buffer.remaining() >= 4)
              return buffer.getInt();
          else
              return (int) readPrimitiveSlowly(4);
      }
  
      @Override
      public long readLong() throws IOException
      {
          if (buffer.remaining() >= 8)
              return buffer.getLong();
          else
              return readPrimitiveSlowly(8);
      }
  
      @Override
      public long readVInt() throws IOException
      {
          return VIntCoding.decodeZigZag64(readUnsignedVInt());
      }
  
      @Override
      public int readVInt32() throws IOException
      {
          return VIntCoding.checkedCast(VIntCoding.decodeZigZag64(readUnsignedVInt()));
      }
  
      @Override
      public long readUnsignedVInt() throws IOException
      {
          //If 9 bytes aren't available use the slow path in VIntCoding
          if (buffer.remaining() < 9)
              return VIntCoding.readUnsignedVInt(this);
  
          byte firstByte = buffer.get();
  
          //Bail out early if this is one byte, necessary or it fails later
          if (firstByte >= 0)
              return firstByte;
  
          int extraBytes = VIntCoding.numberOfExtraBytesToRead(firstByte);
  
          int position = buffer.position();
          int extraBits = extraBytes * 8;
  
          long retval = buffer.getLong(position);
          if (buffer.order() == ByteOrder.LITTLE_ENDIAN)
              retval = Long.reverseBytes(retval);
          buffer.position(position + extraBytes);
  
          // truncate the bytes we read in excess of those we needed
          retval >>>= 64 - extraBits;
          // remove the non-value bits from the first byte
          firstByte &= VIntCoding.firstByteValueMask(extraBytes);
          // shift the first byte up to its correct position
          retval |= (long) firstByte << extraBits;
          return retval;
      }
  
      @Override
      public long readLeastSignificantBytes(int bytes) throws IOException
      {
          if (buffer.remaining() < 8)
              return super.readLeastSignificantBytes(bytes);
  
          long retval = buffer.getLong(buffer.position());
          retval >>>= 64 - (bytes * 8);
          buffer.position(buffer.position() + bytes);
          return retval;
      }
  
      @Override
      public int readUnsignedVInt32() throws IOException
      {
          return VIntCoding.checkedCast(readUnsignedVInt());
      }
  
      @Override
      public float readFloat() throws IOException
      {
          if (buffer.remaining() >= 4)
              return buffer.getFloat();
          else
              return Float.intBitsToFloat((int)readPrimitiveSlowly(4));
      }
  
      @Override
      public double readDouble() throws IOException
      {
          if (buffer.remaining() >= 8)
              return buffer.getDouble();
          else
              return Double.longBitsToDouble(readPrimitiveSlowly(8));
      }
  
      @Override
      public String readLine() throws IOException
      {
          throw new UnsupportedOperationException();
      }
  
      @Override
      public String readUTF() throws IOException
      {
          return DataInputStream.readUTF(this);
      }
  
      @Override
      public int read() throws IOException
      {
          try
          {
              return readUnsignedByte();
          }
          catch (EOFException ex)
          {
              return -1;
          }
      }
  }
  