/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Lucene.Net.Support;
using Document = Lucene.Net.Documents.Document;
using FieldSelector = Lucene.Net.Documents.FieldSelector;
using Directory = Lucene.Net.Store.Directory;
using Lock = Lucene.Net.Store.Lock;
using LockObtainFailedException = Lucene.Net.Store.LockObtainFailedException;
using DefaultSimilarity = Lucene.Net.Search.DefaultSimilarity;
namespace Lucene.Net.Index
{
/// An IndexReader which reads indexes with multiple segments.
public class DirectoryReader:IndexReader
{
/*new*/ private class AnonymousClassFindSegmentsFile:SegmentInfos.FindSegmentsFile
{
private void InitBlock(bool readOnly, IndexDeletionPolicy deletionPolicy, int termInfosIndexDivisor)
{
this.readOnly = readOnly;
this.deletionPolicy = deletionPolicy;
this.termInfosIndexDivisor = termInfosIndexDivisor;
}
private bool readOnly;
private IndexDeletionPolicy deletionPolicy;
private int termInfosIndexDivisor;
internal AnonymousClassFindSegmentsFile(bool readOnly, Lucene.Net.Index.IndexDeletionPolicy deletionPolicy, int termInfosIndexDivisor, Lucene.Net.Store.Directory Param1):base(Param1)
{
InitBlock(readOnly, deletionPolicy, termInfosIndexDivisor);
}
public /*protected internal*/ override System.Object DoBody(System.String segmentFileName)
{
var infos = new SegmentInfos();
infos.Read(directory, segmentFileName);
if (readOnly)
return new ReadOnlyDirectoryReader(directory, infos, deletionPolicy, termInfosIndexDivisor);
else
return new DirectoryReader(directory, infos, deletionPolicy, false, termInfosIndexDivisor);
}
}
private class AnonymousClassFindSegmentsFile1:SegmentInfos.FindSegmentsFile
{
private void InitBlock(bool openReadOnly, DirectoryReader enclosingInstance)
{
this.openReadOnly = openReadOnly;
this.enclosingInstance = enclosingInstance;
}
private bool openReadOnly;
private DirectoryReader enclosingInstance;
public DirectoryReader Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
internal AnonymousClassFindSegmentsFile1(bool openReadOnly, DirectoryReader enclosingInstance, Lucene.Net.Store.Directory Param1):base(Param1)
{
InitBlock(openReadOnly, enclosingInstance);
}
public /*protected internal*/ override System.Object DoBody(System.String segmentFileName)
{
var infos = new SegmentInfos();
infos.Read(directory, segmentFileName);
return Enclosing_Instance.DoReopen(infos, false, openReadOnly);
}
}
protected internal Directory internalDirectory;
protected internal bool readOnly;
internal IndexWriter writer;
private IndexDeletionPolicy deletionPolicy;
private readonly HashSet synced = new HashSet();
private Lock writeLock;
private readonly SegmentInfos segmentInfos;
private readonly SegmentInfos segmentInfosStart;
private bool stale;
private readonly int termInfosIndexDivisor;
private bool rollbackHasChanges;
private SegmentReader[] subReaders;
private int[] starts; // 1st docno for each segment
private System.Collections.Generic.IDictionary normsCache = new HashMap();
private int maxDoc = 0;
private int numDocs = - 1;
private bool hasDeletions = false;
// Max version in index as of when we opened; this can be
// > our current segmentInfos version in case we were
// opened on a past IndexCommit:
private long maxIndexVersion;
internal static IndexReader Open(Directory directory, IndexDeletionPolicy deletionPolicy, IndexCommit commit, bool readOnly, int termInfosIndexDivisor)
{
return (IndexReader) new AnonymousClassFindSegmentsFile(readOnly, deletionPolicy, termInfosIndexDivisor, directory).Run(commit);
}
/// Construct reading the named set of readers.
internal DirectoryReader(Directory directory, SegmentInfos sis, IndexDeletionPolicy deletionPolicy, bool readOnly, int termInfosIndexDivisor)
{
internalDirectory = directory;
this.readOnly = readOnly;
this.segmentInfos = sis;
this.deletionPolicy = deletionPolicy;
this.termInfosIndexDivisor = termInfosIndexDivisor;
if (!readOnly)
{
// We assume that this segments_N was previously
// properly sync'd:
synced.UnionWith(sis.Files(directory, true));
}
// To reduce the chance of hitting FileNotFound
// (and having to retry), we open segments in
// reverse because IndexWriter merges & deletes
// the newest segments first.
var readers = new SegmentReader[sis.Count];
for (int i = sis.Count - 1; i >= 0; i--)
{
bool success = false;
try
{
readers[i] = SegmentReader.Get(readOnly, sis.Info(i), termInfosIndexDivisor);
success = true;
}
finally
{
if (!success)
{
// Close all readers we had opened:
for (i++; i < sis.Count; i++)
{
try
{
readers[i].Close();
}
catch (System.Exception)
{
// keep going - we want to clean up as much as possible
}
}
}
}
}
Initialize(readers);
}
// Used by near real-time search
internal DirectoryReader(IndexWriter writer, SegmentInfos infos, int termInfosIndexDivisor)
{
this.internalDirectory = writer.Directory;
this.readOnly = true;
segmentInfos = infos;
segmentInfosStart = (SegmentInfos) infos.Clone();
this.termInfosIndexDivisor = termInfosIndexDivisor;
if (!readOnly)
{
// We assume that this segments_N was previously
// properly sync'd:
synced.UnionWith(infos.Files(internalDirectory, true));
}
// IndexWriter synchronizes externally before calling
// us, which ensures infos will not change; so there's
// no need to process segments in reverse order
int numSegments = infos.Count;
var readers = new SegmentReader[numSegments];
Directory dir = writer.Directory;
int upto = 0;
for (int i = 0; i < numSegments; i++)
{
bool success = false;
try
{
SegmentInfo info = infos.Info(i);
if (info.dir == dir)
{
readers[upto++] = writer.readerPool.GetReadOnlyClone(info, true, termInfosIndexDivisor);
}
success = true;
}
finally
{
if (!success)
{
// Close all readers we had opened:
for (upto--; upto >= 0; upto--)
{
try
{
readers[upto].Close();
}
catch (System.Exception)
{
// keep going - we want to clean up as much as possible
}
}
}
}
}
this.writer = writer;
if (upto < readers.Length)
{
// This means some segments were in a foreign Directory
var newReaders = new SegmentReader[upto];
Array.Copy(readers, 0, newReaders, 0, upto);
readers = newReaders;
}
Initialize(readers);
}
/// This constructor is only used for
internal DirectoryReader(Directory directory, SegmentInfos infos, SegmentReader[] oldReaders, int[] oldStarts,
IEnumerable> oldNormsCache, bool readOnly, bool doClone, int termInfosIndexDivisor)
{
this.internalDirectory = directory;
this.readOnly = readOnly;
this.segmentInfos = infos;
this.termInfosIndexDivisor = termInfosIndexDivisor;
if (!readOnly)
{
// We assume that this segments_N was previously
// properly sync'd:
synced.UnionWith(infos.Files(directory, true));
}
// we put the old SegmentReaders in a map, that allows us
// to lookup a reader using its segment name
IDictionary segmentReaders = new HashMap();
if (oldReaders != null)
{
// create a Map SegmentName->SegmentReader
for (int i = 0; i < oldReaders.Length; i++)
{
segmentReaders[oldReaders[i].SegmentName] = i;
}
}
var newReaders = new SegmentReader[infos.Count];
// remember which readers are shared between the old and the re-opened
// DirectoryReader - we have to incRef those readers
var readerShared = new bool[infos.Count];
for (int i = infos.Count - 1; i >= 0; i--)
{
// find SegmentReader for this segment
if (!segmentReaders.ContainsKey(infos.Info(i).name))
{
// this is a new segment, no old SegmentReader can be reused
newReaders[i] = null;
}
else
{
// there is an old reader for this segment - we'll try to reopen it
newReaders[i] = oldReaders[segmentReaders[infos.Info(i).name]];
}
bool success = false;
try
{
SegmentReader newReader;
if (newReaders[i] == null || infos.Info(i).GetUseCompoundFile() != newReaders[i].SegmentInfo.GetUseCompoundFile())
{
// We should never see a totally new segment during cloning
System.Diagnostics.Debug.Assert(!doClone);
// this is a new reader; in case we hit an exception we can close it safely
newReader = SegmentReader.Get(readOnly, infos.Info(i), termInfosIndexDivisor);
}
else
{
newReader = newReaders[i].ReopenSegment(infos.Info(i), doClone, readOnly);
}
if (newReader == newReaders[i])
{
// this reader will be shared between the old and the new one,
// so we must incRef it
readerShared[i] = true;
newReader.IncRef();
}
else
{
readerShared[i] = false;
newReaders[i] = newReader;
}
success = true;
}
finally
{
if (!success)
{
for (i++; i < infos.Count; i++)
{
if (newReaders[i] != null)
{
try
{
if (!readerShared[i])
{
// this is a new subReader that is not used by the old one,
// we can close it
newReaders[i].Close();
}
else
{
// this subReader is also used by the old reader, so instead
// closing we must decRef it
newReaders[i].DecRef();
}
}
catch (System.IO.IOException)
{
// keep going - we want to clean up as much as possible
}
}
}
}
}
}
// initialize the readers to calculate maxDoc before we try to reuse the old normsCache
Initialize(newReaders);
// try to copy unchanged norms from the old normsCache to the new one
if (oldNormsCache != null)
{
foreach(var entry in oldNormsCache)
{
String field = entry.Key;
if (!HasNorms(field))
{
continue;
}
byte[] oldBytes = entry.Value;
var bytes = new byte[MaxDoc];
for (int i = 0; i < subReaders.Length; i++)
{
int oldReaderIndex = segmentReaders[subReaders[i].SegmentName];
// this SegmentReader was not re-opened, we can copy all of its norms
if (segmentReaders.ContainsKey(subReaders[i].SegmentName) &&
(oldReaders[oldReaderIndex] == subReaders[i]
|| oldReaders[oldReaderIndex].norms[field] == subReaders[i].norms[field]))
{
// we don't have to synchronize here: either this constructor is called from a SegmentReader,
// in which case no old norms cache is present, or it is called from MultiReader.reopen(),
// which is synchronized
Array.Copy(oldBytes, oldStarts[oldReaderIndex], bytes, starts[i], starts[i + 1] - starts[i]);
}
else
{
subReaders[i].Norms(field, bytes, starts[i]);
}
}
normsCache[field] = bytes; // update cache
}
}
}
private void Initialize(SegmentReader[] subReaders)
{
this.subReaders = subReaders;
starts = new int[subReaders.Length + 1]; // build starts array
for (int i = 0; i < subReaders.Length; i++)
{
starts[i] = maxDoc;
maxDoc += subReaders[i].MaxDoc; // compute maxDocs
if (subReaders[i].HasDeletions)
hasDeletions = true;
}
starts[subReaders.Length] = maxDoc;
if (!readOnly)
{
maxIndexVersion = SegmentInfos.ReadCurrentVersion(internalDirectory);
}
}
public override Object Clone()
{
lock (this)
{
try
{
return Clone(readOnly); // Preserve current readOnly
}
catch (Exception ex)
{
throw new SystemException(ex.Message, ex); // TODO: why rethrow this way?
}
}
}
public override IndexReader Clone(bool openReadOnly)
{
lock (this)
{
DirectoryReader newReader = DoReopen((SegmentInfos) segmentInfos.Clone(), true, openReadOnly);
if (this != newReader)
{
newReader.deletionPolicy = deletionPolicy;
}
newReader.writer = writer;
// If we're cloning a non-readOnly reader, move the
// writeLock (if there is one) to the new reader:
if (!openReadOnly && writeLock != null)
{
// In near real-time search, reader is always readonly
System.Diagnostics.Debug.Assert(writer == null);
newReader.writeLock = writeLock;
newReader.hasChanges = hasChanges;
newReader.hasDeletions = hasDeletions;
writeLock = null;
hasChanges = false;
}
return newReader;
}
}
public override IndexReader Reopen()
{
// Preserve current readOnly
return DoReopen(readOnly, null);
}
public override IndexReader Reopen(bool openReadOnly)
{
return DoReopen(openReadOnly, null);
}
public override IndexReader Reopen(IndexCommit commit)
{
return DoReopen(true, commit);
}
private IndexReader DoReopenFromWriter(bool openReadOnly, IndexCommit commit)
{
System.Diagnostics.Debug.Assert(readOnly);
if (!openReadOnly)
{
throw new System.ArgumentException("a reader obtained from IndexWriter.getReader() can only be reopened with openReadOnly=true (got false)");
}
if (commit != null)
{
throw new System.ArgumentException("a reader obtained from IndexWriter.getReader() cannot currently accept a commit");
}
// TODO: right now we *always* make a new reader; in
// the future we could have write make some effort to
// detect that no changes have occurred
return writer.GetReader();
}
internal virtual IndexReader DoReopen(bool openReadOnly, IndexCommit commit)
{
EnsureOpen();
System.Diagnostics.Debug.Assert(commit == null || openReadOnly);
// If we were obtained by writer.getReader(), re-ask the
// writer to get a new reader.
if (writer != null)
{
return DoReopenFromWriter(openReadOnly, commit);
}
else
{
return DoReopenNoWriter(openReadOnly, commit);
}
}
private IndexReader DoReopenNoWriter(bool openReadOnly, IndexCommit commit)
{
lock (this)
{
if (commit == null)
{
if (hasChanges)
{
// We have changes, which means we are not readOnly:
System.Diagnostics.Debug.Assert(readOnly == false);
// and we hold the write lock:
System.Diagnostics.Debug.Assert(writeLock != null);
// so no other writer holds the write lock, which
// means no changes could have been done to the index:
System.Diagnostics.Debug.Assert(IsCurrent());
if (openReadOnly)
{
return Clone(openReadOnly);
}
else
{
return this;
}
}
else if (IsCurrent())
{
if (openReadOnly != readOnly)
{
// Just fallback to clone
return Clone(openReadOnly);
}
else
{
return this;
}
}
}
else
{
if (internalDirectory != commit.Directory)
throw new System.IO.IOException("the specified commit does not match the specified Directory");
if (segmentInfos != null && commit.SegmentsFileName.Equals(segmentInfos.GetCurrentSegmentFileName()))
{
if (readOnly != openReadOnly)
{
// Just fallback to clone
return Clone(openReadOnly);
}
else
{
return this;
}
}
}
return (IndexReader)new AnonymousFindSegmentsFile(internalDirectory, openReadOnly, this).Run(commit);
}
}
class AnonymousFindSegmentsFile : SegmentInfos.FindSegmentsFile
{
readonly DirectoryReader enclosingInstance;
readonly bool openReadOnly;
readonly Directory dir;
public AnonymousFindSegmentsFile(Directory directory, bool openReadOnly, DirectoryReader dirReader) : base(directory)
{
this.dir = directory;
this.openReadOnly = openReadOnly;
enclosingInstance = dirReader;
}
public override object DoBody(string segmentFileName)
{
var infos = new SegmentInfos();
infos.Read(dir, segmentFileName);
return enclosingInstance.DoReopen(infos, false, openReadOnly);
}
}
private DirectoryReader DoReopen(SegmentInfos infos, bool doClone, bool openReadOnly)
{
lock (this)
{
DirectoryReader reader;
if (openReadOnly)
{
reader = new ReadOnlyDirectoryReader(internalDirectory, infos, subReaders, starts, normsCache, doClone, termInfosIndexDivisor);
}
else
{
reader = new DirectoryReader(internalDirectory, infos, subReaders, starts, normsCache, false, doClone, termInfosIndexDivisor);
}
return reader;
}
}
/// Version number when this IndexReader was opened.
public override long Version
{
get
{
EnsureOpen();
return segmentInfos.Version;
}
}
public override ITermFreqVector[] GetTermFreqVectors(int n)
{
EnsureOpen();
int i = ReaderIndex(n); // find segment num
return subReaders[i].GetTermFreqVectors(n - starts[i]); // dispatch to segment
}
public override ITermFreqVector GetTermFreqVector(int n, System.String field)
{
EnsureOpen();
int i = ReaderIndex(n); // find segment num
return subReaders[i].GetTermFreqVector(n - starts[i], field);
}
public override void GetTermFreqVector(int docNumber, System.String field, TermVectorMapper mapper)
{
EnsureOpen();
int i = ReaderIndex(docNumber); // find segment num
subReaders[i].GetTermFreqVector(docNumber - starts[i], field, mapper);
}
public override void GetTermFreqVector(int docNumber, TermVectorMapper mapper)
{
EnsureOpen();
int i = ReaderIndex(docNumber); // find segment num
subReaders[i].GetTermFreqVector(docNumber - starts[i], mapper);
}
/// Checks is the index is optimized (if it has a single segment and no deletions)
/// <c>true</c> if the index is optimized; <c>false</c> otherwise
public override bool IsOptimized()
{
EnsureOpen();
return segmentInfos.Count == 1 && !HasDeletions;
}
public override int NumDocs()
{
// Don't call ensureOpen() here (it could affect performance)
// NOTE: multiple threads may wind up init'ing
// numDocs... but that's harmless
if (numDocs == - 1)
{
// check cache
int n = subReaders.Sum(t => t.NumDocs()); // cache miss--recompute
numDocs = n;
}
return numDocs;
}
public override int MaxDoc
{
get
{
// Don't call ensureOpen() here (it could affect performance)
return maxDoc;
}
}
// inherit javadoc
public override Document Document(int n, FieldSelector fieldSelector)
{
EnsureOpen();
int i = ReaderIndex(n); // find segment num
return subReaders[i].Document(n - starts[i], fieldSelector); // dispatch to segment reader
}
public override bool IsDeleted(int n)
{
// Don't call ensureOpen() here (it could affect performance)
int i = ReaderIndex(n); // find segment num
return subReaders[i].IsDeleted(n - starts[i]); // dispatch to segment reader
}
public override bool HasDeletions
{
get
{
// Don't call ensureOpen() here (it could affect performance)
return hasDeletions;
}
}
protected internal override void DoDelete(int n)
{
numDocs = - 1; // invalidate cache
int i = ReaderIndex(n); // find segment num
subReaders[i].DeleteDocument(n - starts[i]); // dispatch to segment reader
hasDeletions = true;
}
protected internal override void DoUndeleteAll()
{
foreach (SegmentReader t in subReaders)
t.UndeleteAll();
hasDeletions = false;
numDocs = - 1; // invalidate cache
}
private int ReaderIndex(int n)
{
// find reader for doc n:
return ReaderIndex(n, this.starts, this.subReaders.Length);
}
internal static int ReaderIndex(int n, int[] starts, int numSubReaders)
{
// find reader for doc n:
int lo = 0; // search starts array
int hi = numSubReaders - 1; // for first element less
while (hi >= lo)
{
int mid = Number.URShift((lo + hi), 1);
int midValue = starts[mid];
if (n < midValue)
hi = mid - 1;
else if (n > midValue)
lo = mid + 1;
else
{
// found a match
while (mid + 1 < numSubReaders && starts[mid + 1] == midValue)
{
mid++; // scan to last match
}
return mid;
}
}
return hi;
}
public override bool HasNorms(System.String field)
{
EnsureOpen();
return subReaders.Any(t => t.HasNorms(field));
}
public override byte[] Norms(System.String field)
{
lock (this)
{
EnsureOpen();
byte[] bytes = normsCache[field];
if (bytes != null)
return bytes; // cache hit
if (!HasNorms(field))
return null;
bytes = new byte[MaxDoc];
for (int i = 0; i < subReaders.Length; i++)
subReaders[i].Norms(field, bytes, starts[i]);
normsCache[field] = bytes; // update cache
return bytes;
}
}
public override void Norms(System.String field, byte[] result, int offset)
{
lock (this)
{
EnsureOpen();
byte[] bytes = normsCache[field];
if (bytes == null && !HasNorms(field))
{
byte val = DefaultSimilarity.EncodeNorm(1.0f);
for (int index = offset; index < result.Length; index++)
result.SetValue(val, index);
}
else if (bytes != null)
{
// cache hit
Array.Copy(bytes, 0, result, offset, MaxDoc);
}
else
{
for (int i = 0; i < subReaders.Length; i++)
{
// read from segments
subReaders[i].Norms(field, result, offset + starts[i]);
}
}
}
}
protected internal override void DoSetNorm(int n, System.String field, byte value_Renamed)
{
lock (normsCache)
{
normsCache.Remove(field); // clear cache
}
int i = ReaderIndex(n); // find segment num
subReaders[i].SetNorm(n - starts[i], field, value_Renamed); // dispatch
}
public override TermEnum Terms()
{
EnsureOpen();
return new MultiTermEnum(this, subReaders, starts, null);
}
public override TermEnum Terms(Term term)
{
EnsureOpen();
return new MultiTermEnum(this, subReaders, starts, term);
}
public override int DocFreq(Term t)
{
EnsureOpen();
int total = 0; // sum freqs in segments
for (int i = 0; i < subReaders.Length; i++)
total += subReaders[i].DocFreq(t);
return total;
}
public override TermDocs TermDocs()
{
EnsureOpen();
return new MultiTermDocs(this, subReaders, starts);
}
public override TermPositions TermPositions()
{
EnsureOpen();
return new MultiTermPositions(this, subReaders, starts);
}
/// Tries to acquire the WriteLock on this directory. this method is only valid if this IndexReader is directory
/// owner.
///
///
/// StaleReaderException if the index has changed since this reader was opened
/// CorruptIndexException if the index is corrupt
/// Lucene.Net.Store.LockObtainFailedException
/// if another writer has this index open (write.lock could not be
/// obtained)
///
/// IOException if there is a low-level IO error
protected internal override void AcquireWriteLock()
{
if (readOnly)
{
// NOTE: we should not reach this code w/ the core
// IndexReader classes; however, an external subclass
// of IndexReader could reach this.
ReadOnlySegmentReader.NoWrite();
}
if (segmentInfos != null)
{
EnsureOpen();
if (stale)
throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
if (this.writeLock == null)
{
Lock writeLock = internalDirectory.MakeLock(IndexWriter.WRITE_LOCK_NAME);
if (!writeLock.Obtain(IndexWriter.WRITE_LOCK_TIMEOUT))
// obtain write lock
{
throw new LockObtainFailedException("Index locked for write: " + writeLock);
}
this.writeLock = writeLock;
// we have to check whether index has changed since this reader was opened.
// if so, this reader is no longer valid for
// deletion
if (SegmentInfos.ReadCurrentVersion(internalDirectory) > maxIndexVersion)
{
stale = true;
this.writeLock.Release();
this.writeLock = null;
throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
}
}
}
}
/// Commit changes resulting from delete, undeleteAll, or setNorm operations
///
/// If an exception is hit, then either no changes or all changes will have been committed to the index (transactional
/// semantics).
///
///
/// IOException if there is a low-level IO error
protected internal override void DoCommit(IDictionary commitUserData)
{
if (hasChanges)
{
segmentInfos.UserData = commitUserData;
// Default deleter (for backwards compatibility) is
// KeepOnlyLastCommitDeleter:
var deleter = new IndexFileDeleter(internalDirectory, deletionPolicy ?? new KeepOnlyLastCommitDeletionPolicy(), segmentInfos, null, null, synced);
segmentInfos.UpdateGeneration(deleter.LastSegmentInfos);
// Checkpoint the state we are about to change, in
// case we have to roll back:
StartCommit();
bool success = false;
try
{
foreach (SegmentReader t in subReaders)
t.Commit();
// Sync all files we just wrote
foreach(string fileName in segmentInfos.Files(internalDirectory, false))
{
if(!synced.Contains(fileName))
{
System.Diagnostics.Debug.Assert(internalDirectory.FileExists(fileName));
internalDirectory.Sync(fileName);
synced.Add(fileName);
}
}
segmentInfos.Commit(internalDirectory);
success = true;
}
finally
{
if (!success)
{
// Rollback changes that were made to
// SegmentInfos but failed to get [fully]
// committed. This way this reader instance
// remains consistent (matched to what's
// actually in the index):
RollbackCommit();
// Recompute deletable files & remove them (so
// partially written .del files, etc, are
// removed):
deleter.Refresh();
}
}
// Have the deleter remove any now unreferenced
// files due to this commit:
deleter.Checkpoint(segmentInfos, true);
deleter.Dispose();
maxIndexVersion = segmentInfos.Version;
if (writeLock != null)
{
writeLock.Release(); // release write lock
writeLock = null;
}
}
hasChanges = false;
}
internal virtual void StartCommit()
{
rollbackHasChanges = hasChanges;
foreach (SegmentReader t in subReaders)
{
t.StartCommit();
}
}
internal virtual void RollbackCommit()
{
hasChanges = rollbackHasChanges;
foreach (SegmentReader t in subReaders)
{
t.RollbackCommit();
}
}
public override IDictionary CommitUserData
{
get
{
EnsureOpen();
return segmentInfos.UserData;
}
}
public override bool IsCurrent()
{
EnsureOpen();
if (writer == null || writer.IsClosed())
{
// we loaded SegmentInfos from the directory
return SegmentInfos.ReadCurrentVersion(internalDirectory) == segmentInfos.Version;
}
else
{
return writer.NrtIsCurrent(segmentInfosStart);
}
}
protected internal override void DoClose()
{
lock (this)
{
System.IO.IOException ioe = null;
normsCache = null;
foreach (SegmentReader t in subReaders)
{
// try to close each reader, even if an exception is thrown
try
{
t.DecRef();
}
catch (System.IO.IOException e)
{
if (ioe == null)
ioe = e;
}
}
// NOTE: only needed in case someone had asked for
// FieldCache for top-level reader (which is generally
// not a good idea):
Search.FieldCache_Fields.DEFAULT.Purge(this);
// throw the first exception
if (ioe != null)
throw ioe;
}
}
public override ICollection GetFieldNames(IndexReader.FieldOption fieldNames)
{
EnsureOpen();
return GetFieldNames(fieldNames, this.subReaders);
}
internal static ICollection GetFieldNames(IndexReader.FieldOption fieldNames, IndexReader[] subReaders)
{
// maintain a unique set of field names
ISet fieldSet = Support.Compatibility.SetFactory.CreateHashSet();
foreach (IndexReader reader in subReaders)
{
fieldSet.UnionWith(reader.GetFieldNames(fieldNames));
}
return fieldSet;
}
public override IndexReader[] GetSequentialSubReaders()
{
return subReaders;
}
/// Returns the directory this index resides in.
public override Directory Directory()
{
// Don't ensureOpen here -- in certain cases, when a
// cloned/reopened reader needs to commit, it may call
// this method on the closed original reader
return internalDirectory;
}
public override int TermInfosIndexDivisor
{
get { return termInfosIndexDivisor; }
}
/// Expert: return the IndexCommit that this reader has opened.
///
/// WARNING: this API is new and experimental and may suddenly change.
///
public override IndexCommit IndexCommit
{
get { return new ReaderCommit(segmentInfos, internalDirectory); }
}
///
///
public static new ICollection ListCommits(Directory dir)
{
String[] files = dir.ListAll();
ICollection commits = new List();
var latest = new SegmentInfos();
latest.Read(dir);
long currentGen = latest.Generation;
commits.Add(new ReaderCommit(latest, dir));
foreach (string fileName in files)
{
if (fileName.StartsWith(IndexFileNames.SEGMENTS) && !fileName.Equals(IndexFileNames.SEGMENTS_GEN) && SegmentInfos.GenerationFromSegmentsFileName(fileName) < currentGen)
{
var sis = new SegmentInfos();
try
{
// IOException allowed to throw there, in case
// segments_N is corrupt
sis.Read(dir, fileName);
}
catch (System.IO.FileNotFoundException)
{
// LUCENE-948: on NFS (and maybe others), if
// you have writers switching back and forth
// between machines, it's very likely that the
// dir listing will be stale and will claim a
// file segments_X exists when in fact it
// doesn't. So, we catch this and handle it
// as if the file does not exist
sis = null;
}
if (sis != null)
commits.Add(new ReaderCommit(sis, dir));
}
}
return commits;
}
private sealed class ReaderCommit:IndexCommit
{
private readonly String segmentsFileName;
private readonly ICollection files;
private readonly Directory dir;
private readonly long generation;
private readonly long version;
private readonly bool isOptimized;
private readonly IDictionary userData;
internal ReaderCommit(SegmentInfos infos, Directory dir)
{
segmentsFileName = infos.GetCurrentSegmentFileName();
this.dir = dir;
userData = infos.UserData;
files = infos.Files(dir, true);
version = infos.Version;
generation = infos.Generation;
isOptimized = infos.Count == 1 && !infos.Info(0).HasDeletions();
}
public override string ToString()
{
return "DirectoryReader.ReaderCommit(" + segmentsFileName + ")";
}
public override bool IsOptimized
{
get { return isOptimized; }
}
public override string SegmentsFileName
{
get { return segmentsFileName; }
}
public override ICollection FileNames
{
get { return files; }
}
public override Directory Directory
{
get { return dir; }
}
public override long Version
{
get { return version; }
}
public override long Generation
{
get { return generation; }
}
public override bool IsDeleted
{
get { return false; }
}
public override IDictionary UserData
{
get { return userData; }
}
public override void Delete()
{
throw new System.NotSupportedException("This IndexCommit does not support deletions");
}
}
internal class MultiTermEnum:TermEnum
{
internal IndexReader topReader; // used for matching TermEnum to TermDocs
private readonly SegmentMergeQueue queue;
private Term term;
private int docFreq;
internal SegmentMergeInfo[] matchingSegments; // null terminated array of matching segments
public MultiTermEnum(IndexReader topReader, IndexReader[] readers, int[] starts, Term t)
{
this.topReader = topReader;
queue = new SegmentMergeQueue(readers.Length);
matchingSegments = new SegmentMergeInfo[readers.Length + 1];
for (int i = 0; i < readers.Length; i++)
{
IndexReader reader = readers[i];
TermEnum termEnum = t != null ? reader.Terms(t) : reader.Terms();
var smi = new SegmentMergeInfo(starts[i], termEnum, reader) {ord = i};
if (t == null?smi.Next():termEnum.Term != null)
queue.Add(smi);
// initialize queue
else
smi.Dispose();
}
if (t != null && queue.Size() > 0)
{
Next();
}
}
public override bool Next()
{
foreach (SegmentMergeInfo smi in matchingSegments)
{
if (smi == null)
break;
if (smi.Next())
queue.Add(smi);
else
smi.Dispose(); // done with segment
}
int numMatchingSegments = 0;
matchingSegments[0] = null;
SegmentMergeInfo top = queue.Top();
if (top == null)
{
term = null;
return false;
}
term = top.term;
docFreq = 0;
while (top != null && term.CompareTo(top.term) == 0)
{
matchingSegments[numMatchingSegments++] = top;
queue.Pop();
docFreq += top.termEnum.DocFreq(); // increment freq
top = queue.Top();
}
matchingSegments[numMatchingSegments] = null;
return true;
}
public override Term Term
{
get { return term; }
}
public override int DocFreq()
{
return docFreq;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
queue.Dispose();
}
}
}
internal class MultiTermDocs : TermDocs
{
internal IndexReader topReader; // used for matching TermEnum to TermDocs
protected internal IndexReader[] readers;
protected internal int[] starts;
protected internal Term term;
protected internal int base_Renamed = 0;
protected internal int pointer = 0;
private readonly TermDocs[] readerTermDocs;
protected internal TermDocs current; // == readerTermDocs[pointer]
private MultiTermEnum tenum; // the term enum used for seeking... can be null
internal int matchingSegmentPos; // position into the matching segments from tenum
internal SegmentMergeInfo smi; // current segment mere info... can be null
public MultiTermDocs(IndexReader topReader, IndexReader[] r, int[] s)
{
this.topReader = topReader;
readers = r;
starts = s;
readerTermDocs = new TermDocs[r.Length];
}
public virtual int Doc
{
get { return base_Renamed + current.Doc; }
}
public virtual int Freq
{
get { return current.Freq; }
}
public virtual void Seek(Term term)
{
this.term = term;
this.base_Renamed = 0;
this.pointer = 0;
this.current = null;
this.tenum = null;
this.smi = null;
this.matchingSegmentPos = 0;
}
public virtual void Seek(TermEnum termEnum)
{
Seek(termEnum.Term);
var multiTermEnum = termEnum as MultiTermEnum;
if (multiTermEnum != null)
{
tenum = multiTermEnum;
if (topReader != tenum.topReader)
tenum = null;
}
}
public virtual bool Next()
{
for (; ; )
{
if (current != null && current.Next())
{
return true;
}
else if (pointer < readers.Length)
{
if (tenum != null)
{
smi = tenum.matchingSegments[matchingSegmentPos++];
if (smi == null)
{
pointer = readers.Length;
return false;
}
pointer = smi.ord;
}
base_Renamed = starts[pointer];
current = TermDocs(pointer++);
}
else
{
return false;
}
}
}
/// Optimized implementation.
public virtual int Read(int[] docs, int[] freqs)
{
while (true)
{
while (current == null)
{
if (pointer < readers.Length)
{
// try next segment
if (tenum != null)
{
smi = tenum.matchingSegments[matchingSegmentPos++];
if (smi == null)
{
pointer = readers.Length;
return 0;
}
pointer = smi.ord;
}
base_Renamed = starts[pointer];
current = TermDocs(pointer++);
}
else
{
return 0;
}
}
int end = current.Read(docs, freqs);
if (end == 0)
{
// none left in segment
current = null;
}
else
{
// got some
int b = base_Renamed; // adjust doc numbers
for (int i = 0; i < end; i++)
docs[i] += b;
return end;
}
}
}
/* A Possible future optimization could skip entire segments */
public virtual bool SkipTo(int target)
{
for (; ; )
{
if (current != null && current.SkipTo(target - base_Renamed))
{
return true;
}
else if (pointer < readers.Length)
{
if (tenum != null)
{
SegmentMergeInfo smi = tenum.matchingSegments[matchingSegmentPos++];
if (smi == null)
{
pointer = readers.Length;
return false;
}
pointer = smi.ord;
}
base_Renamed = starts[pointer];
current = TermDocs(pointer++);
}
else
return false;
}
}
private TermDocs TermDocs(int i)
{
TermDocs result = readerTermDocs[i] ?? (readerTermDocs[i] = TermDocs(readers[i]));
if (smi != null)
{
System.Diagnostics.Debug.Assert((smi.ord == i));
System.Diagnostics.Debug.Assert((smi.termEnum.Term.Equals(term)));
result.Seek(smi.termEnum);
}
else
{
result.Seek(term);
}
return result;
}
protected internal virtual TermDocs TermDocs(IndexReader reader)
{
return term == null ? reader.TermDocs(null):reader.TermDocs();
}
public virtual void Close()
{
Dispose();
}
public virtual void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (TermDocs t in readerTermDocs)
{
if (t != null)
t.Close();
}
}
}
}
internal class MultiTermPositions:MultiTermDocs, TermPositions
{
public MultiTermPositions(IndexReader topReader, IndexReader[] r, int[] s):base(topReader, r, s)
{
}
protected internal override TermDocs TermDocs(IndexReader reader)
{
return reader.TermPositions();
}
public virtual int NextPosition()
{
return ((TermPositions) current).NextPosition();
}
public virtual int PayloadLength
{
get { return ((TermPositions) current).PayloadLength; }
}
public virtual byte[] GetPayload(byte[] data, int offset)
{
return ((TermPositions) current).GetPayload(data, offset);
}
// TODO: Remove warning after API has been finalized
public virtual bool IsPayloadAvailable
{
get { return ((TermPositions) current).IsPayloadAvailable; }
}
}
}
}