blob: 4b7951e4058e0e342184fe87d6795c6ac33babd7 [file] [log] [blame]
using J2N.IO;
using J2N.Numerics;
using Lucene.Net.Diagnostics;
using Lucene.Net.Util.Fst;
using System;
using System.IO;
using System.Runtime.CompilerServices;
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 <see cref="IndexInput"/> implementation that uses an array
/// of <see cref="ByteBuffer"/>s to represent a file.
/// <para/>
/// Because Java's <see cref="ByteBuffer"/> uses an <see cref="int"/> to address the
/// values, it's necessary to access a file greater
/// <see cref="int.MaxValue"/> in size using multiple byte buffers.
/// <para/>
/// For efficiency, this class requires that the buffers
/// are a power-of-two (<c>chunkSizePower</c>).
/// </summary>
public abstract class ByteBufferIndexInput : IndexInput
{
private ByteBuffer[] buffers;
private readonly long chunkSizeMask;
private readonly int chunkSizePower;
private int offset;
private long length;
private string sliceDescription;
private int curBufIndex;
private ByteBuffer curBuf; // redundant for speed: buffers[curBufIndex]
private bool isClone = false;
// LUCENENET: Using ConditionalWeakTable rather than WeakIdenityMap. ConditionalWeakTable
// uses RuntimeHelpers.GetHashCode() to find the item, so technically, it IS an identity collection.
private ConditionalWeakTable<ByteBufferIndexInput, BoolRefWrapper> clones;
private class BoolRefWrapper
{
private readonly bool value;
// .NET port: this is needed as bool is not a reference type
public BoolRefWrapper(bool value)
{
this.value = value;
}
public static implicit operator bool(BoolRefWrapper value)
{
return value.value;
}
public static implicit operator BoolRefWrapper(bool value)
{
return new BoolRefWrapper(value);
}
}
internal ByteBufferIndexInput(string resourceDescription, ByteBuffer[] buffers, long length, int chunkSizePower, bool trackClones)
: base(resourceDescription)
{
//this.buffers = buffers; // LUCENENET: this is set in SetBuffers()
this.length = length;
this.chunkSizePower = chunkSizePower;
this.chunkSizeMask = (1L << chunkSizePower) - 1L;
// LUCENENET: Using ConditionalWeakTable rather than WeakIdenityMap. ConditionalWeakTable
// uses RuntimeHelpers.GetHashCode() to find the item, so technically, it IS an identity collection.
this.clones = trackClones ? new ConditionalWeakTable<ByteBufferIndexInput, BoolRefWrapper>() : null;
if (Debugging.AssertsEnabled)
{
Debugging.Assert(chunkSizePower >= 0 && chunkSizePower <= 30);
Debugging.Assert(length.TripleShift(chunkSizePower) < int.MaxValue);
}
// LUCENENET specific: MMapIndexInput calls SetBuffers() to populate
// the buffers, so we need to skip that call if it is null here, and
// do the seek inside SetBuffers()
if (buffers != null)
{
SetBuffers(buffers);
}
}
// LUCENENET specific for encapsulating buffers field.
internal void SetBuffers(ByteBuffer[] buffers) // necessary for MMapIndexInput
{
this.buffers = buffers;
Seek(0L);
}
public override sealed byte ReadByte()
{
// LUCENENET: Refactored to avoid calls on invalid conditions instead of
// catching and re-throwing exceptions in the normal workflow.
EnsureOpen();
if (curBuf.HasRemaining)
{
return curBuf.Get();
}
do
{
curBufIndex++;
if (curBufIndex >= buffers.Length)
{
throw new EndOfStreamException("read past EOF: " + this);
}
curBuf = buffers[curBufIndex];
curBuf.Position = 0;
} while (!curBuf.HasRemaining);
return curBuf.Get();
}
public override sealed void ReadBytes(byte[] b, int offset, int len)
{
// LUCENENET: Refactored to avoid calls on invalid conditions instead of
// catching and re-throwing exceptions in the normal workflow.
EnsureOpen();
int curAvail = curBuf.Remaining;
if (len <= curAvail)
{
curBuf.Get(b, offset, len);
}
else
{
while (len > curAvail)
{
curBuf.Get(b, offset, curAvail);
len -= curAvail;
offset += curAvail;
curBufIndex++;
if (curBufIndex >= buffers.Length)
{
throw new EndOfStreamException("read past EOF: " + this);
}
curBuf = buffers[curBufIndex];
curBuf.Position = 0;
curAvail = curBuf.Remaining;
}
curBuf.Get(b, offset, len);
}
}
/// <summary>
/// NOTE: this was readShort() in Lucene
/// </summary>
public override sealed short ReadInt16()
{
// LUCENENET: Refactored to avoid calls on invalid conditions instead of
// catching and re-throwing exceptions in the normal workflow.
EnsureOpen();
if (curBuf.Remaining >= 2)
{
return curBuf.GetInt16();
}
return base.ReadInt16();
}
/// <summary>
/// NOTE: this was readInt() in Lucene
/// </summary>
public override sealed int ReadInt32()
{
// LUCENENET: Refactored to avoid calls on invalid conditions instead of
// catching and re-throwing exceptions in the normal workflow.
EnsureOpen();
if (curBuf.Remaining >= 4)
{
return curBuf.GetInt32();
}
return base.ReadInt32();
}
/// <summary>
/// NOTE: this was readLong() in Lucene
/// </summary>
public override sealed long ReadInt64()
{
// LUCENENET: Refactored to avoid calls on invalid conditions instead of
// catching and re-throwing exceptions in the normal workflow.
EnsureOpen();
if (curBuf.Remaining >= 8)
{
return curBuf.GetInt64();
}
return base.ReadInt64();
}
public override sealed long GetFilePointer()
{
// LUCENENET: Refactored to avoid calls on invalid conditions instead of
// catching and re-throwing exceptions in the normal workflow.
EnsureOpen();
return (((long)curBufIndex) << chunkSizePower) + curBuf.Position - offset;
}
public override sealed void Seek(long pos)
{
// necessary in case offset != 0 and pos < 0, but pos >= -offset
if (pos < 0L)
{
throw new ArgumentException("Seeking to negative position: " + this);
}
pos += offset;
// we use >> here to preserve negative, so we will catch AIOOBE,
// in case pos + offset overflows.
int bi = (int)(pos >> chunkSizePower);
try
{
ByteBuffer b = buffers[bi];
b.Position = ((int)(pos & chunkSizeMask));
// write values, on exception all is unchanged
this.curBufIndex = bi;
this.curBuf = b;
}
catch (IndexOutOfRangeException)
{
throw new EndOfStreamException("seek past EOF: " + this);
}
catch (ArgumentException)
{
throw new EndOfStreamException("seek past EOF: " + this);
}
catch (NullReferenceException)
{
throw new ObjectDisposedException(this.GetType().FullName, "Already closed: " + this);
}
}
public override sealed long Length => length;
public override sealed object Clone()
{
ByteBufferIndexInput clone = BuildSlice(0L, this.length);
try
{
clone.Seek(GetFilePointer());
}
catch (IOException ioe)
{
throw new Exception("Should never happen: " + this, ioe);
}
return clone;
}
/// <summary>
/// Creates a slice of this index input, with the given description, offset, and length. The slice is seeked to the beginning.
/// </summary>
public ByteBufferIndexInput Slice(string sliceDescription, long offset, long length)
{
if (isClone) // well we could, but this is stupid
{
throw new InvalidOperationException("cannot slice() " + sliceDescription + " from a cloned IndexInput: " + this);
}
ByteBufferIndexInput clone = BuildSlice(offset, length);
clone.sliceDescription = sliceDescription;
try
{
clone.Seek(0L);
}
catch (IOException ioe)
{
throw new Exception("Should never happen: " + this, ioe);
}
return clone;
}
private ByteBufferIndexInput BuildSlice(long offset, long length)
{
if (buffers == null)
{
throw new ObjectDisposedException(this.GetType().FullName, "Already closed: " + this);
}
if (offset < 0 || length < 0 || offset + length > this.length)
{
throw new ArgumentException("slice() " + sliceDescription + " out of bounds: offset=" + offset + ",length=" + length + ",fileLength=" + this.length + ": " + this);
}
// include our own offset into the final offset:
offset += this.offset;
ByteBufferIndexInput clone = (ByteBufferIndexInput)base.Clone();
clone.isClone = true;
// we keep clone.clones, so it shares the same map with original and we have no additional cost on clones
if (Debugging.AssertsEnabled) Debugging.Assert(clone.clones == this.clones);
clone.buffers = BuildSlice(buffers, offset, length);
clone.offset = (int)(offset & chunkSizeMask);
clone.length = length;
// register the new clone in our clone list to clean it up on closing:
if (clones != null)
{
this.clones.Add(clone, true);
}
return clone;
}
/// <summary>
/// Returns a sliced view from a set of already-existing buffers:
/// the last buffer's <see cref="J2N.IO.Buffer.Limit"/> will be correct, but
/// you must deal with <paramref name="offset"/> separately (the first buffer will not be adjusted)
/// </summary>
private ByteBuffer[] BuildSlice(ByteBuffer[] buffers, long offset, long length)
{
long sliceEnd = offset + length;
int startIndex = (int)(offset.TripleShift(chunkSizePower));
int endIndex = (int)(sliceEnd.TripleShift(chunkSizePower));
// we always allocate one more slice, the last one may be a 0 byte one
ByteBuffer[] slices = new ByteBuffer[endIndex - startIndex + 1];
for (int i = 0; i < slices.Length; i++)
{
slices[i] = buffers[startIndex + i].Duplicate();
}
// set the last buffer's limit for the sliced view.
slices[slices.Length - 1].Limit = ((int)(sliceEnd & chunkSizeMask));
return slices;
}
private void UnsetBuffers()
{
buffers = null;
curBuf = null;
curBufIndex = 0;
}
// LUCENENET specific - rather than using all of this exception catching nonsense
// for control flow, we check whether we are disposed first.
private void EnsureOpen()
{
if (buffers == null || curBuf == null)
{
throw new ObjectDisposedException(this.GetType().FullName, "Already closed: " + this);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
try
{
if (buffers == null)
{
return;
}
// make local copy, then un-set early
ByteBuffer[] bufs = buffers;
UnsetBuffers();
if (clones != null)
{
clones.Remove(this);
}
if (isClone)
{
return;
}
// for extra safety unset also all clones' buffers:
if (clones != null)
{
// LUCENENET: Since .NET will GC types that go out of scope automatically,
// this isn't strictly necessary. However, we are doing it anyway when
// the enumerator is available (.NET Standard 2.1+)
#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
foreach (var pair in clones)
{
if (Debugging.AssertsEnabled) Debugging.Assert(pair.Key.isClone);
pair.Key.UnsetBuffers();
}
this.clones.Clear();
#endif
this.clones = null; // LUCENENET: de-reference the table so it can be GC'd
}
foreach (ByteBuffer b in bufs)
{
FreeBuffer(b); // LUCENENET: This calls Dispose() when necessary
}
}
finally
{
UnsetBuffers();
}
}
}
/// <summary>
/// Called when the contents of a buffer will be no longer needed.
/// </summary>
protected abstract void FreeBuffer(ByteBuffer b);
public override sealed string ToString()
{
if (sliceDescription != null)
{
return base.ToString() + " [slice=" + sliceDescription + "]";
}
else
{
return base.ToString();
}
}
}
}