| using Lucene.Net.Diagnostics; |
| using System; |
| using System.IO; |
| |
| namespace Lucene.Net.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. |
| */ |
| |
| /// <summary> |
| /// Base implementation class for buffered <see cref="IndexInput"/>. </summary> |
| public abstract class BufferedIndexInput : IndexInput |
| { |
| /// <summary> |
| /// Default buffer size set to <see cref="BUFFER_SIZE"/>. </summary> |
| public const int BUFFER_SIZE = 1024; |
| |
| // The normal read buffer size defaults to 1024, but |
| // increasing this during merging seems to yield |
| // performance gains. However we don't want to increase |
| // it too much because there are quite a few |
| // BufferedIndexInputs created during merging. See |
| // LUCENE-888 for details. |
| /// <summary> |
| /// A buffer size for merges set to <see cref="MERGE_BUFFER_SIZE"/>. |
| /// </summary> |
| public const int MERGE_BUFFER_SIZE = 4096; |
| |
| private int bufferSize = BUFFER_SIZE; |
| |
| protected byte[] m_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 |
| |
| public override sealed byte ReadByte() |
| { |
| if (bufferPosition >= bufferLength) |
| { |
| Refill(); |
| } |
| return m_buffer[bufferPosition++]; |
| } |
| |
| public BufferedIndexInput(string resourceDesc) |
| : this(resourceDesc, BUFFER_SIZE) |
| { |
| } |
| |
| public BufferedIndexInput(string resourceDesc, IOContext context) |
| : this(resourceDesc, GetBufferSize(context)) |
| { |
| } |
| |
| /// <summary> |
| /// Inits <see cref="BufferedIndexInput"/> with a specific <paramref name="bufferSize"/> </summary> |
| public BufferedIndexInput(string resourceDesc, int bufferSize) |
| : base(resourceDesc) |
| { |
| CheckBufferSize(bufferSize); |
| this.bufferSize = bufferSize; |
| } |
| |
| /// <summary> |
| /// Change the buffer size used by this <see cref="IndexInput"/> </summary> |
| public void SetBufferSize(int newSize) |
| { |
| if (Debugging.AssertsEnabled) Debugging.Assert(m_buffer == null || bufferSize == m_buffer.Length, () => "buffer=" + m_buffer + " bufferSize=" + bufferSize + " buffer.length=" + (m_buffer != null ? m_buffer.Length : 0)); |
| if (newSize != bufferSize) |
| { |
| CheckBufferSize(newSize); |
| bufferSize = newSize; |
| if (m_buffer != null) |
| { |
| // Resize the existing buffer and carefully save as |
| // many bytes as possible starting from the current |
| // bufferPosition |
| byte[] newBuffer = new byte[newSize]; |
| int leftInBuffer = bufferLength - bufferPosition; |
| int numToCopy; |
| if (leftInBuffer > newSize) |
| { |
| numToCopy = newSize; |
| } |
| else |
| { |
| numToCopy = leftInBuffer; |
| } |
| Array.Copy(m_buffer, bufferPosition, newBuffer, 0, numToCopy); |
| bufferStart += bufferPosition; |
| bufferPosition = 0; |
| bufferLength = numToCopy; |
| NewBuffer(newBuffer); |
| } |
| } |
| } |
| |
| protected virtual void NewBuffer(byte[] newBuffer) |
| { |
| // Subclasses can do something here |
| m_buffer = newBuffer; |
| } |
| |
| /// <summary> |
| /// Returns buffer size. |
| /// </summary> |
| /// <seealso cref="SetBufferSize(int)"/> |
| public int BufferSize => bufferSize; |
| |
| private void CheckBufferSize(int bufferSize) |
| { |
| if (bufferSize <= 0) |
| { |
| throw new ArgumentException("bufferSize must be greater than 0 (got " + bufferSize + ")"); |
| } |
| } |
| |
| public override sealed void ReadBytes(byte[] b, int offset, int len) |
| { |
| ReadBytes(b, offset, len, true); |
| } |
| |
| public override sealed void ReadBytes(byte[] b, int offset, int len, bool useBuffer) |
| { |
| int available = bufferLength - bufferPosition; |
| if (len <= available) |
| { |
| // the buffer contains enough data to satisfy this request |
| if (len > 0) // to allow b to be null if len is 0... |
| { |
| Buffer.BlockCopy(m_buffer, bufferPosition, b, offset, len); |
| } |
| bufferPosition += len; |
| } |
| else |
| { |
| // the buffer does not have enough data. First serve all we've got. |
| if (available > 0) |
| { |
| Buffer.BlockCopy(m_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: |
| Buffer.BlockCopy(m_buffer, 0, b, offset, bufferLength); |
| throw new EndOfStreamException("read past EOF: " + this); |
| } |
| else |
| { |
| Buffer.BlockCopy(m_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 EndOfStreamException("read past EOF: " + this); |
| } |
| ReadInternal(b, offset, len); |
| bufferStart = after; |
| bufferPosition = 0; |
| bufferLength = 0; // trigger refill() on read |
| } |
| } |
| } |
| |
| /// <summary> |
| /// NOTE: this was readShort() in Lucene |
| /// </summary> |
| public override sealed short ReadInt16() |
| { |
| if (2 <= (bufferLength - bufferPosition)) |
| { |
| return (short)(((m_buffer[bufferPosition++] & 0xFF) << 8) | (m_buffer[bufferPosition++] & 0xFF)); |
| } |
| else |
| { |
| return base.ReadInt16(); |
| } |
| } |
| |
| /// <summary> |
| /// NOTE: this was readInt() in Lucene |
| /// </summary> |
| public override sealed int ReadInt32() |
| { |
| if (4 <= (bufferLength - bufferPosition)) |
| { |
| return ((m_buffer[bufferPosition++] & 0xFF) << 24) | ((m_buffer[bufferPosition++] & 0xFF) << 16) |
| | ((m_buffer[bufferPosition++] & 0xFF) << 8) | (m_buffer[bufferPosition++] & 0xFF); |
| } |
| else |
| { |
| return base.ReadInt32(); |
| } |
| } |
| |
| /// <summary> |
| /// NOTE: this was readLong() in Lucene |
| /// </summary> |
| public override sealed long ReadInt64() |
| { |
| if (8 <= (bufferLength - bufferPosition)) |
| { |
| int i1 = ((m_buffer[bufferPosition++] & 0xff) << 24) | ((m_buffer[bufferPosition++] & 0xff) << 16) |
| | ((m_buffer[bufferPosition++] & 0xff) << 8) | (m_buffer[bufferPosition++] & 0xff); |
| int i2 = ((m_buffer[bufferPosition++] & 0xff) << 24) | ((m_buffer[bufferPosition++] & 0xff) << 16) |
| | ((m_buffer[bufferPosition++] & 0xff) << 8) | (m_buffer[bufferPosition++] & 0xff); |
| return (((long)i1) << 32) | (i2 & 0xFFFFFFFFL); |
| } |
| else |
| { |
| return base.ReadInt64(); |
| } |
| } |
| |
| /// <summary> |
| /// NOTE: this was readVInt() in Lucene |
| /// </summary> |
| public override sealed int ReadVInt32() |
| { |
| if (5 <= (bufferLength - bufferPosition)) |
| { |
| byte b = m_buffer[bufferPosition++]; |
| if ((sbyte)b >= 0) |
| { |
| return b; |
| } |
| int i = b & 0x7F; |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7F) << 7; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7F) << 14; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7F) << 21; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| // Warning: the next ands use 0x0F / 0xF0 - beware copy/paste errors: |
| i |= (b & 0x0F) << 28; |
| if ((b & 0xF0) == 0) |
| { |
| return i; |
| } |
| throw new IOException("Invalid VInt32 detected (too many bits)"); |
| } |
| else |
| { |
| return base.ReadVInt32(); |
| } |
| } |
| |
| /// <summary> |
| /// NOTE: this was readVLong() in Lucene |
| /// </summary> |
| public override sealed long ReadVInt64() |
| { |
| if (9 <= bufferLength - bufferPosition) |
| { |
| byte b = m_buffer[bufferPosition++]; |
| if ((sbyte)b >= 0) |
| { |
| return b; |
| } |
| long i = b & 0x7FL; |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 7; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 14; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 21; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 28; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 35; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 42; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 49; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| b = m_buffer[bufferPosition++]; |
| i |= (b & 0x7FL) << 56; |
| if ((sbyte)b >= 0) |
| { |
| return i; |
| } |
| throw new IOException("Invalid VInt64 detected (negative values disallowed)"); |
| } |
| else |
| { |
| return base.ReadVInt64(); |
| } |
| } |
| |
| private void Refill() |
| { |
| 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 EndOfStreamException("read past EOF: " + this); |
| } |
| |
| if (m_buffer == null) |
| { |
| NewBuffer(new byte[bufferSize]); // allocate buffer lazily |
| SeekInternal(bufferStart); |
| } |
| ReadInternal(m_buffer, 0, newLength); |
| bufferLength = newLength; |
| bufferStart = start; |
| bufferPosition = 0; |
| } |
| |
| /// <summary> |
| /// Expert: implements buffer refill. Reads bytes from the current position |
| /// in the input. </summary> |
| /// <param name="b"> the array to read bytes into </param> |
| /// <param name="offset"> the offset in the array to start storing bytes </param> |
| /// <param name="length"> the number of bytes to read </param> |
| protected abstract void ReadInternal(byte[] b, int offset, int length); |
| |
| public override sealed long GetFilePointer() |
| { |
| return bufferStart + bufferPosition; |
| } |
| |
| public override sealed void Seek(long pos) |
| { |
| 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); |
| } |
| } |
| |
| /// <summary> |
| /// Expert: implements seek. Sets current position in this file, where the |
| /// next <see cref="ReadInternal(byte[], int, int)"/> will occur. </summary> |
| /// <seealso cref="ReadInternal(byte[], int, int)"/> |
| protected abstract void SeekInternal(long pos); |
| |
| public override object Clone() |
| { |
| BufferedIndexInput clone = (BufferedIndexInput)base.Clone(); |
| |
| clone.m_buffer = null; |
| clone.bufferLength = 0; |
| clone.bufferPosition = 0; |
| clone.bufferStart = GetFilePointer(); |
| |
| return clone; |
| } |
| |
| /// <summary> |
| /// Flushes the in-memory buffer to the given output, copying at most |
| /// <paramref name="numBytes"/>. |
| /// <para/> |
| /// <b>NOTE:</b> this method does not refill the buffer, however it does |
| /// advance the buffer position. |
| /// </summary> |
| /// <returns> the number of bytes actually flushed from the in-memory buffer. </returns> |
| protected int FlushBuffer(IndexOutput @out, long numBytes) |
| { |
| int toCopy = bufferLength - bufferPosition; |
| if (toCopy > numBytes) |
| { |
| toCopy = (int)numBytes; |
| } |
| if (toCopy > 0) |
| { |
| @out.WriteBytes(m_buffer, bufferPosition, toCopy); |
| bufferPosition += toCopy; |
| } |
| return toCopy; |
| } |
| |
| /// <summary> |
| /// Returns default buffer sizes for the given <see cref="IOContext"/> |
| /// </summary> |
| public static int GetBufferSize(IOContext context) // LUCENENET NOTE: Renamed from BufferSize to prevent naming conflict |
| { |
| switch (context.Context) |
| { |
| case IOContext.UsageContext.MERGE: |
| return MERGE_BUFFER_SIZE; |
| |
| default: |
| return BUFFER_SIZE; |
| } |
| } |
| } |
| } |