| using J2N.Collections.Generic.Extensions; |
| using Lucene.Net.Diagnostics; |
| using System; |
| using System.Collections.Generic; |
| using System.IO; |
| using System.Text; |
| |
| namespace Lucene.Net.Index |
| { |
| /* |
| * 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 Directory = Lucene.Net.Store.Directory; |
| using IOContext = Lucene.Net.Store.IOContext; |
| using IOUtils = Lucene.Net.Util.IOUtils; |
| |
| internal sealed class StandardDirectoryReader : DirectoryReader |
| { |
| private readonly IndexWriter writer; |
| private readonly SegmentInfos segmentInfos; |
| private readonly int termInfosIndexDivisor; |
| private readonly bool applyAllDeletes; |
| |
| /// <summary> |
| /// called only from static <c>Open()</c> methods </summary> |
| internal StandardDirectoryReader(Directory directory, AtomicReader[] readers, IndexWriter writer, SegmentInfos sis, int termInfosIndexDivisor, bool applyAllDeletes) |
| : base(directory, readers) |
| { |
| this.writer = writer; |
| this.segmentInfos = sis; |
| this.termInfosIndexDivisor = termInfosIndexDivisor; |
| this.applyAllDeletes = applyAllDeletes; |
| } |
| |
| /// <summary> |
| /// called from <c>DirectoryReader.Open(...)</c> methods </summary> |
| internal static DirectoryReader Open(Directory directory, IndexCommit commit, int termInfosIndexDivisor) |
| { |
| return (DirectoryReader)new FindSegmentsFileAnonymousClass(directory, termInfosIndexDivisor).Run(commit); |
| } |
| |
| private class FindSegmentsFileAnonymousClass : SegmentInfos.FindSegmentsFile |
| { |
| private readonly int termInfosIndexDivisor; |
| |
| public FindSegmentsFileAnonymousClass(Directory directory, int termInfosIndexDivisor) |
| : base(directory) |
| { |
| this.termInfosIndexDivisor = termInfosIndexDivisor; |
| } |
| |
| protected internal override object DoBody(string segmentFileName) |
| { |
| var sis = new SegmentInfos(); |
| sis.Read(directory, segmentFileName); |
| var readers = new SegmentReader[sis.Count]; |
| for (int i = sis.Count - 1; i >= 0; i--) |
| { |
| IOException prior = null; |
| bool success = false; |
| try |
| { |
| readers[i] = new SegmentReader(sis.Info(i), termInfosIndexDivisor, IOContext.READ); |
| success = true; |
| } |
| catch (IOException ex) |
| { |
| prior = ex; |
| } |
| finally |
| { |
| if (!success) |
| { |
| IOUtils.DisposeWhileHandlingException(prior, readers); |
| } |
| } |
| } |
| return new StandardDirectoryReader(directory, readers, null, sis, termInfosIndexDivisor, false); |
| } |
| } |
| |
| /// <summary> |
| /// Used by near real-time search </summary> |
| internal static DirectoryReader Open(IndexWriter writer, SegmentInfos infos, bool applyAllDeletes) |
| { |
| // 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; |
| |
| IList<SegmentReader> readers = new List<SegmentReader>(); |
| Directory dir = writer.Directory; |
| |
| SegmentInfos segmentInfos = (SegmentInfos)infos.Clone(); |
| int infosUpto = 0; |
| bool success = false; |
| try |
| { |
| for (int i = 0; i < numSegments; i++) |
| { |
| // NOTE: important that we use infos not |
| // segmentInfos here, so that we are passing the |
| // actual instance of SegmentInfoPerCommit in |
| // IndexWriter's segmentInfos: |
| SegmentCommitInfo info = infos.Info(i); |
| if (Debugging.AssertsEnabled) Debugging.Assert(info.Info.Dir == dir); |
| ReadersAndUpdates rld = writer.readerPool.Get(info, true); |
| try |
| { |
| SegmentReader reader = rld.GetReadOnlyClone(IOContext.READ); |
| if (reader.NumDocs > 0 || writer.KeepFullyDeletedSegments) |
| { |
| // Steal the ref: |
| readers.Add(reader); |
| infosUpto++; |
| } |
| else |
| { |
| reader.DecRef(); |
| segmentInfos.Remove(infosUpto); |
| } |
| } |
| finally |
| { |
| writer.readerPool.Release(rld); |
| } |
| } |
| |
| writer.IncRefDeleter(segmentInfos); |
| |
| StandardDirectoryReader result = new StandardDirectoryReader(dir, readers.ToArray(), writer, segmentInfos, writer.Config.ReaderTermsIndexDivisor, applyAllDeletes); |
| success = true; |
| return result; |
| } |
| finally |
| { |
| if (!success) |
| { |
| foreach (SegmentReader r in readers) |
| { |
| try |
| { |
| r.DecRef(); |
| } |
| catch (Exception) // LUCENENET: IDE0059: Remove unnecessary value assignment |
| { |
| // ignore any exception that is thrown here to not mask any original |
| // exception. |
| } |
| } |
| } |
| } |
| } |
| |
| /// <summary> |
| /// This constructor is only used for <see cref="DoOpenIfChanged(SegmentInfos)"/> </summary> |
| private static DirectoryReader Open(Directory directory, SegmentInfos infos, IList<IndexReader> oldReaders, int termInfosIndexDivisor) // LUCENENET: Changed from AtomicReader to IndexReader to eliminate casting from the 1 place this is called from |
| { |
| // we put the old SegmentReaders in a map, that allows us |
| // to lookup a reader using its segment name |
| IDictionary<string, int?> segmentReaders = new Dictionary<string, int?>(); |
| |
| if (oldReaders != null) |
| { |
| // create a Map SegmentName->SegmentReader |
| for (int i = 0, c = oldReaders.Count; i < c; i++) |
| { |
| SegmentReader sr = (SegmentReader)oldReaders[i]; |
| segmentReaders[sr.SegmentName] = i; |
| } |
| } |
| |
| SegmentReader[] newReaders = new SegmentReader[infos.Count]; |
| |
| // remember which readers are shared between the old and the re-opened |
| // DirectoryReader - we have to incRef those readers |
| bool[] readerShared = new bool[infos.Count]; |
| |
| for (int i = infos.Count - 1; i >= 0; i--) |
| { |
| // find SegmentReader for this segment |
| if (!segmentReaders.TryGetValue(infos.Info(i).Info.Name, out int? oldReaderIndex) || oldReaderIndex == null) |
| { |
| // 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] = (SegmentReader)oldReaders[(int)oldReaderIndex]; |
| } |
| |
| bool success = false; |
| Exception prior = null; |
| try |
| { |
| SegmentReader newReader; |
| if (newReaders[i] == null || infos.Info(i).Info.UseCompoundFile != newReaders[i].SegmentInfo.Info.UseCompoundFile) |
| { |
| // this is a new reader; in case we hit an exception we can close it safely |
| newReader = new SegmentReader(infos.Info(i), termInfosIndexDivisor, IOContext.READ); |
| readerShared[i] = false; |
| newReaders[i] = newReader; |
| } |
| else |
| { |
| if (newReaders[i].SegmentInfo.DelGen == infos.Info(i).DelGen && newReaders[i].SegmentInfo.FieldInfosGen == infos.Info(i).FieldInfosGen) |
| { |
| // No change; this reader will be shared between |
| // the old and the new one, so we must incRef |
| // it: |
| readerShared[i] = true; |
| newReaders[i].IncRef(); |
| } |
| else |
| { |
| // there are changes to the reader, either liveDocs or DV updates |
| readerShared[i] = false; |
| // Steal the ref returned by SegmentReader ctor: |
| if (Debugging.AssertsEnabled) |
| { |
| Debugging.Assert(infos.Info(i).Info.Dir == newReaders[i].SegmentInfo.Info.Dir); |
| Debugging.Assert(infos.Info(i).HasDeletions || infos.Info(i).HasFieldUpdates); |
| } |
| if (newReaders[i].SegmentInfo.DelGen == infos.Info(i).DelGen) |
| { |
| // only DV updates |
| newReaders[i] = new SegmentReader(infos.Info(i), newReaders[i], newReaders[i].LiveDocs, newReaders[i].NumDocs); |
| } |
| else |
| { |
| // both DV and liveDocs have changed |
| newReaders[i] = new SegmentReader(infos.Info(i), newReaders[i]); |
| } |
| } |
| } |
| success = true; |
| } |
| catch (Exception ex) |
| { |
| prior = ex; |
| } |
| 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].Dispose(); |
| } |
| else |
| { |
| // this subReader is also used by the old reader, so instead |
| // closing we must decRef it |
| newReaders[i].DecRef(); |
| } |
| } |
| catch (Exception t) |
| { |
| if (prior == null) |
| { |
| prior = t; |
| } |
| } |
| } |
| } |
| } |
| // throw the first exception |
| IOUtils.ReThrow(prior); |
| } |
| } |
| return new StandardDirectoryReader(directory, newReaders, null, infos, termInfosIndexDivisor, false); |
| } |
| |
| public override string ToString() |
| { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.Append(this.GetType().Name); |
| buffer.Append('('); |
| string segmentsFile = segmentInfos.GetSegmentsFileName(); |
| if (segmentsFile != null) |
| { |
| buffer.Append(segmentsFile).Append(":").Append(segmentInfos.Version); |
| } |
| if (writer != null) |
| { |
| buffer.Append(":nrt"); |
| } |
| foreach (AtomicReader r in GetSequentialSubReaders()) |
| { |
| buffer.Append(' '); |
| buffer.Append(r); |
| } |
| buffer.Append(')'); |
| return buffer.ToString(); |
| } |
| |
| protected internal override DirectoryReader DoOpenIfChanged() |
| { |
| return DoOpenIfChanged((IndexCommit)null); |
| } |
| |
| protected internal override DirectoryReader DoOpenIfChanged(IndexCommit commit) |
| { |
| EnsureOpen(); |
| |
| // If we were obtained by writer.getReader(), re-ask the |
| // writer to get a new reader. |
| if (writer != null) |
| { |
| return DoOpenFromWriter(commit); |
| } |
| else |
| { |
| return DoOpenNoWriter(commit); |
| } |
| } |
| |
| protected internal override DirectoryReader DoOpenIfChanged(IndexWriter writer, bool applyAllDeletes) |
| { |
| EnsureOpen(); |
| if (writer == this.writer && applyAllDeletes == this.applyAllDeletes) |
| { |
| return DoOpenFromWriter(null); |
| } |
| else |
| { |
| return writer.GetReader(applyAllDeletes); |
| } |
| } |
| |
| private DirectoryReader DoOpenFromWriter(IndexCommit commit) |
| { |
| if (commit != null) |
| { |
| return DoOpenFromCommit(commit); |
| } |
| |
| if (writer.NrtIsCurrent(segmentInfos)) |
| { |
| return null; |
| } |
| |
| DirectoryReader reader = writer.GetReader(applyAllDeletes); |
| |
| // If in fact no changes took place, return null: |
| if (reader.Version == segmentInfos.Version) |
| { |
| reader.DecRef(); |
| return null; |
| } |
| |
| return reader; |
| } |
| |
| private DirectoryReader DoOpenNoWriter(IndexCommit commit) |
| { |
| if (commit == null) |
| { |
| if (IsCurrent()) |
| { |
| return null; |
| } |
| } |
| else |
| { |
| if (m_directory != commit.Directory) |
| { |
| throw new IOException("the specified commit does not match the specified Directory"); |
| } |
| if (segmentInfos != null && commit.SegmentsFileName.Equals(segmentInfos.GetSegmentsFileName(), StringComparison.Ordinal)) |
| { |
| return null; |
| } |
| } |
| |
| return DoOpenFromCommit(commit); |
| } |
| |
| private DirectoryReader DoOpenFromCommit(IndexCommit commit) |
| { |
| return (DirectoryReader)new FindSegmentsFileAnonymousClass2(this, m_directory).Run(commit); |
| } |
| |
| private class FindSegmentsFileAnonymousClass2 : SegmentInfos.FindSegmentsFile |
| { |
| private readonly StandardDirectoryReader outerInstance; |
| |
| public FindSegmentsFileAnonymousClass2(StandardDirectoryReader outerInstance, Directory directory) |
| : base(directory) |
| { |
| this.outerInstance = outerInstance; |
| } |
| |
| protected internal override object DoBody(string segmentFileName) |
| { |
| SegmentInfos infos = new SegmentInfos(); |
| infos.Read(outerInstance.m_directory, segmentFileName); |
| return outerInstance.DoOpenIfChanged(infos); |
| } |
| } |
| |
| internal DirectoryReader DoOpenIfChanged(SegmentInfos infos) |
| { |
| return StandardDirectoryReader.Open(m_directory, infos, GetSequentialSubReaders(), termInfosIndexDivisor); |
| } |
| |
| public override long Version |
| { |
| get |
| { |
| EnsureOpen(); |
| return segmentInfos.Version; |
| } |
| } |
| |
| public override bool IsCurrent() |
| { |
| EnsureOpen(); |
| if (writer == null || writer.IsClosed) |
| { |
| // Fully read the segments file: this ensures that it's |
| // completely written so that if |
| // IndexWriter.prepareCommit has been called (but not |
| // yet commit), then the reader will still see itself as |
| // current: |
| SegmentInfos sis = new SegmentInfos(); |
| sis.Read(m_directory); |
| |
| // we loaded SegmentInfos from the directory |
| return sis.Version == segmentInfos.Version; |
| } |
| else |
| { |
| return writer.NrtIsCurrent(segmentInfos); |
| } |
| } |
| |
| protected internal override void DoClose() |
| { |
| Exception firstExc = null; |
| foreach (AtomicReader r in GetSequentialSubReaders()) |
| { |
| // try to close each reader, even if an exception is thrown |
| try |
| { |
| r.DecRef(); |
| } |
| catch (Exception t) |
| { |
| if (firstExc == null) |
| { |
| firstExc = t; |
| } |
| } |
| } |
| |
| if (writer != null) |
| { |
| try |
| { |
| writer.DecRefDeleter(segmentInfos); |
| } |
| catch (ObjectDisposedException) // LUCENENET: IDE0059: Remove unnecessary value assignment |
| { |
| // this is OK, it just means our original writer was |
| // closed before we were, and this may leave some |
| // un-referenced files in the index, which is |
| // harmless. The next time IW is opened on the |
| // index, it will delete them. |
| } |
| } |
| |
| // throw the first exception |
| IOUtils.ReThrow(firstExc); |
| } |
| |
| public override IndexCommit IndexCommit |
| { |
| get |
| { |
| EnsureOpen(); |
| return new ReaderCommit(segmentInfos, m_directory); |
| } |
| } |
| |
| internal sealed class ReaderCommit : IndexCommit |
| { |
| internal string segmentsFileName; |
| internal ICollection<string> files; |
| internal Directory dir; |
| internal long generation; |
| internal readonly IDictionary<string, string> userData; |
| internal readonly int segmentCount; |
| |
| internal ReaderCommit(SegmentInfos infos, Directory dir) |
| { |
| segmentsFileName = infos.GetSegmentsFileName(); |
| this.dir = dir; |
| userData = infos.UserData; |
| files = infos.GetFiles(dir, true); |
| generation = infos.Generation; |
| segmentCount = infos.Count; |
| } |
| |
| public override string ToString() |
| { |
| return "DirectoryReader.ReaderCommit(" + segmentsFileName + ")"; |
| } |
| |
| public override int SegmentCount => segmentCount; |
| |
| public override string SegmentsFileName => segmentsFileName; |
| |
| public override ICollection<string> FileNames => files; |
| |
| public override Directory Directory => dir; |
| |
| public override long Generation => generation; |
| |
| public override bool IsDeleted => false; |
| |
| public override IDictionary<string, string> UserData => userData; |
| |
| public override void Delete() |
| { |
| throw new NotSupportedException("this IndexCommit does not support deletions"); |
| } |
| } |
| } |
| } |