content: package org.apache.lucene.store;
  
  /**
   * 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.
   */
  
  import java.io.IOException;
  
  /** Base implementation class for buffered {@link IndexInput}. */
  public abstract class BufferedIndexInput extends IndexInput {
  
    /** Default buffer size */
    public static final int BUFFER_SIZE = 1024;
  
    private int bufferSize = BUFFER_SIZE;
    
    protected byte[] buffer;
    
    private long bufferStart = 0;			  // position in file of buffer
    private int bufferLength = 0;			  // end of valid bytes
    private int bufferPosition = 0;		  // next byte to read
  
    @Override
    public byte readByte() throws IOException {
      if (bufferPosition >= bufferLength)
        refill();
      return buffer[bufferPosition++];
    }
  
    /** @deprecated please pass resourceDesc */
    @Deprecated
    public BufferedIndexInput() {
      this("anonymous BuffereIndexInput");
    }
  
    public BufferedIndexInput(String resourceDesc) {
      this(resourceDesc, BUFFER_SIZE);
    }
  
    /** Inits BufferedIndexInput with a specific bufferSize
     *  @deprecated please pass resourceDesc */
    @Deprecated
    public BufferedIndexInput(int bufferSize) {
      this("anonymous BuffereIndexInput", bufferSize);
    }
  
    /** Inits BufferedIndexInput with a specific bufferSize */
    public BufferedIndexInput(String resourceDesc, int bufferSize) {
      super(resourceDesc);
      checkBufferSize(bufferSize);
      this.bufferSize = bufferSize;
    }
  
    /** Change the buffer size used by this IndexInput */
    public void setBufferSize(int newSize) {
      assert buffer == null || bufferSize == buffer.length: "buffer=" + buffer + " bufferSize=" + bufferSize + " buffer.length=" + (buffer != null ? buffer.length : 0);
      if (newSize != bufferSize) {
        checkBufferSize(newSize);
        bufferSize = newSize;
        if (buffer != null) {
          // Resize the existing buffer and carefully save as
          // many bytes as possible starting from the current
          // bufferPosition
          byte[] newBuffer = new byte[newSize];
          final int leftInBuffer = bufferLength-bufferPosition;
          final int numToCopy;
          if (leftInBuffer > newSize)
            numToCopy = newSize;
          else
            numToCopy = leftInBuffer;
          System.arraycopy(buffer, bufferPosition, newBuffer, 0, numToCopy);
          bufferStart += bufferPosition;
          bufferPosition = 0;
          bufferLength = numToCopy;
          newBuffer(newBuffer);
        }
      }
    }
  
    protected void newBuffer(byte[] newBuffer) {
      // Subclasses can do something here
      buffer = newBuffer;
    }
  
    /** Returns buffer size.  @see #setBufferSize */
    public int getBufferSize() {
      return bufferSize;
    }
  
    private void checkBufferSize(int bufferSize) {
      if (bufferSize <= 0)
        throw new IllegalArgumentException("bufferSize must be greater than 0 (got " + bufferSize + ")");
    }
  
    @Override
    public void readBytes(byte[] b, int offset, int len) throws IOException {
      readBytes(b, offset, len, true);
    }
  
    @Override
    public void readBytes(byte[] b, int offset, int len, boolean useBuffer) throws IOException {
  
      if(len <= (bufferLength-bufferPosition)){
        // the buffer contains enough data to satisfy this request
        if(len>0) // to allow b to be null if len is 0...
          System.arraycopy(buffer, bufferPosition, b, offset, len);
        bufferPosition+=len;
      } else {
        // the buffer does not have enough data. First serve all we've got.
        int available = bufferLength - bufferPosition;
        if(available > 0){
          System.arraycopy(buffer, bufferPosition, b, offset, available);
          offset += available;
          len -= available;
          bufferPosition += available;
        }
        // and now, read the remaining 'len' bytes:
        if (useBuffer && len<bufferSize){
          // If the amount left to read is small enough, and
          // we are allowed to use our buffer, do it in the usual
          // buffered way: fill the buffer and copy from it:
          refill();
          if(bufferLength<len){
            // Throw an exception when refill() could not read len bytes:
            System.arraycopy(buffer, 0, b, offset, bufferLength);
            throw new IOException("read past EOF");
          } else {
            System.arraycopy(buffer, 0, b, offset, len);
            bufferPosition=len;
          }
        } else {
          // The amount left to read is larger than the buffer
          // or we've been asked to not use our buffer -
          // there's no performance reason not to read it all
          // at once. Note that unlike the previous code of
          // this function, there is no need to do a seek
          // here, because there's no need to reread what we
          // had in the buffer.
          long after = bufferStart+bufferPosition+len;
          if(after > length())
            throw new IOException("read past EOF");
          readInternal(b, offset, len);
          bufferStart = after;
          bufferPosition = 0;
          bufferLength = 0;                    // trigger refill() on read
        }
      }
    }
    
    @Override
    public int readInt() throws IOException {
      if (4 <= (bufferLength-bufferPosition)) {
        return ((buffer[bufferPosition++] & 0xFF) << 24) | ((buffer[bufferPosition++] & 0xFF) << 16)
          | ((buffer[bufferPosition++] & 0xFF) <<  8) |  (buffer[bufferPosition++] & 0xFF);
      } else {
        return super.readInt();
      }
    }
    
    @Override
    public long readLong() throws IOException {
      if (8 <= (bufferLength-bufferPosition)) {
        final int i1 = ((buffer[bufferPosition++] & 0xff) << 24) | ((buffer[bufferPosition++] & 0xff) << 16) |
          ((buffer[bufferPosition++] & 0xff) << 8) | (buffer[bufferPosition++] & 0xff);
        final int i2 = ((buffer[bufferPosition++] & 0xff) << 24) | ((buffer[bufferPosition++] & 0xff) << 16) |
          ((buffer[bufferPosition++] & 0xff) << 8) | (buffer[bufferPosition++] & 0xff);
        return (((long)i1) << 32) | (i2 & 0xFFFFFFFFL);
      } else {
        return super.readLong();
      }
    }
  
    @Override
    public int readVInt() throws IOException {
      if (5 <= (bufferLength-bufferPosition)) {
        byte b = buffer[bufferPosition++];
        int i = b & 0x7F;
        for (int shift = 7; (b & 0x80) != 0; shift += 7) {
          b = buffer[bufferPosition++];
          i |= (b & 0x7F) << shift;
        }
        return i;
      } else {
        return super.readVInt();
      }
    }
    
    @Override
    public long readVLong() throws IOException {
      if (9 <= bufferLength-bufferPosition) {
        byte b = buffer[bufferPosition++];
        long i = b & 0x7F;
        for (int shift = 7; (b & 0x80) != 0; shift += 7) {
          b = buffer[bufferPosition++];
          i |= (b & 0x7FL) << shift;
        }
        return i;
      } else {
        return super.readVLong();
      }
    }
    
    private void refill() throws IOException {
      long start = bufferStart + bufferPosition;
      long end = start + bufferSize;
      if (end > length())				  // don't read past EOF
        end = length();
      int newLength = (int)(end - start);
      if (newLength <= 0)
        throw new IOException("read past EOF");
  
      if (buffer == null) {
        newBuffer(new byte[bufferSize]);  // allocate buffer lazily
        seekInternal(bufferStart);
      }
      readInternal(buffer, 0, newLength);
      bufferLength = newLength;
      bufferStart = start;
      bufferPosition = 0;
    }
  
    /** Expert: implements buffer refill.  Reads bytes from the current position
     * in the input.
     * @param b the array to read bytes into
     * @param offset the offset in the array to start storing bytes
     * @param length the number of bytes to read
     */
    protected abstract void readInternal(byte[] b, int offset, int length)
            throws IOException;
  
    @Override
    public long getFilePointer() { return bufferStart + bufferPosition; }
  
    @Override
    public void seek(long pos) throws IOException {
      if (pos >= bufferStart && pos < (bufferStart + bufferLength))
        bufferPosition = (int)(pos - bufferStart);  // seek within buffer
      else {
        bufferStart = pos;
        bufferPosition = 0;
        bufferLength = 0;				  // trigger refill() on read()
        seekInternal(pos);
      }
    }
  
    /** Expert: implements seek.  Sets current position in this file, where the
     * next {@link #readInternal(byte[],int,int)} will occur.
     * @see #readInternal(byte[],int,int)
     */
    protected abstract void seekInternal(long pos) throws IOException;
  
    @Override
    public Object clone() {
      BufferedIndexInput clone = (BufferedIndexInput)super.clone();
  
      clone.buffer = null;
      clone.bufferLength = 0;
      clone.bufferPosition = 0;
      clone.bufferStart = getFilePointer();
  
      return clone;
    }
  
    /**
     * Flushes the in-memory bufer to the given output, copying at most
     * <code>numBytes</code>.
     * <p>
     * <b>NOTE:</b> this method does not refill the buffer, however it does
     * advance the buffer position.
     * 
     * @return the number of bytes actually flushed from the in-memory buffer.
     */
    protected int flushBuffer(IndexOutput out, long numBytes) throws IOException {
      int toCopy = bufferLength - bufferPosition;
      if (toCopy > numBytes) {
        toCopy = (int) numBytes;
      }
      if (toCopy > 0) {
        out.writeBytes(buffer, bufferPosition, toCopy);
        bufferPosition += toCopy;
      }
      return toCopy;
    }
    
    @Override
    public void copyBytes(IndexOutput out, long numBytes) throws IOException {
      assert numBytes >= 0: "numBytes=" + numBytes;
  
      while (numBytes > 0) {
        if (bufferLength == bufferPosition) {
          refill();
        }
        numBytes -= flushBuffer(out, numBytes);
      }
    }
    
  }
  