diff --git a/src/Lucene.Net/Search/IndexSearcher.cs b/src/Lucene.Net/Search/IndexSearcher.cs index abdee3c172..b7f0f13bdd 100644 --- a/src/Lucene.Net/Search/IndexSearcher.cs +++ b/src/Lucene.Net/Search/IndexSearcher.cs @@ -1,12 +1,12 @@ -using Lucene.Net.Diagnostics; -using Lucene.Net.Support; +#nullable enable +using Lucene.Net.Diagnostics; using Lucene.Net.Support.Threading; using Lucene.Net.Util; using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Threading; using System.Threading.Tasks; namespace Lucene.Net.Search @@ -80,10 +80,10 @@ public class IndexSearcher /// /// Used with executor - each slice holds a set of leafs executed within one thread - protected readonly LeafSlice[] m_leafSlices; + protected readonly LeafSlice[]? m_leafSlices; // These are only used for multi-threaded search - private readonly TaskScheduler executor; + private readonly TaskScheduler? executor; // the default Similarity private static readonly Similarity defaultSimilarity = new DefaultSimilarity(); @@ -104,8 +104,9 @@ public class IndexSearcher /// /// Creates a searcher searching the provided index. + /// is null. public IndexSearcher(IndexReader r) - : this(r, null) + : this(r, executor: null) { } @@ -117,8 +118,9 @@ public IndexSearcher(IndexReader r) /// /// @lucene.experimental /// - public IndexSearcher(IndexReader r, TaskScheduler executor) - : this(r.Context, executor) + /// is null. + public IndexSearcher(IndexReader r, TaskScheduler? executor) + : this(r?.Context!, executor) { } @@ -132,16 +134,48 @@ public IndexSearcher(IndexReader r, TaskScheduler executor) /// /// @lucene.experimental /// + /// is null. /// /// - public IndexSearcher(IndexReaderContext context, TaskScheduler executor) + public IndexSearcher(IndexReaderContext context, TaskScheduler? executor) + : this(context, executor, allocateLeafSlices: executor is not null) { - if (Debugging.AssertsEnabled) Debugging.Assert(context.IsTopLevel,"IndexSearcher's ReaderContext must be topLevel for reader {0}", context.Reader); + } + + /// + /// LUCENENET specific constructor that can be used by the subclasses to + /// control whether the leaf slices are allocated in the base class or subclass. + /// + /// + /// If is non-null and you choose to skip allocating the leaf slices + /// (i.e. == false), you must + /// set the field in your subclass constructor. + /// This is commonly done by calling + /// and using the result to set . You may wish to do this if you + /// have state to pass into your constructor and need to set it prior to the call to + /// so it is available for use + /// as a member field or property inside a custom override of + /// . + /// + /// is null. + [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "This is a SonarCloud issue")] + [SuppressMessage("CodeQuality", "S1699:Constructors should only call non-overridable methods", Justification = "Required for continuity with Lucene's design")] + protected IndexSearcher(IndexReaderContext context, TaskScheduler? executor, bool allocateLeafSlices) + { + if (context is null) + throw new ArgumentNullException(nameof(context)); + + if (Debugging.AssertsEnabled) Debugging.Assert(context.IsTopLevel, "IndexSearcher's ReaderContext must be topLevel for reader {0}", context.Reader); + reader = context.Reader; this.executor = executor; this.m_readerContext = context; m_leafContexts = context.Leaves; - this.m_leafSlices = executor is null ? null : GetSlicesInternal(m_leafContexts); + + if (allocateLeafSlices) + { + this.m_leafSlices = GetSlices(m_leafContexts); + } } /// @@ -149,6 +183,7 @@ public IndexSearcher(IndexReaderContext context, TaskScheduler executor) /// /// @lucene.experimental /// + /// is null. /// /// public IndexSearcher(IndexReaderContext context) @@ -160,19 +195,14 @@ public IndexSearcher(IndexReaderContext context) /// Expert: Creates an array of leaf slices each holding a subset of the given leaves. /// Each is executed in a single thread. By default there /// will be one per leaf (). - /// - /// NOTE: When overriding this method, be aware that the constructor of this class calls - /// a private method and not this virtual method. So if you need to override - /// the behavior during the initialization, call your own private method from the constructor - /// with whatever custom behavior you need. /// - // LUCENENET specific - renamed to GetSlices to better indicate the purpose of this method + /// is null. protected virtual LeafSlice[] GetSlices(IList leaves) - => GetSlicesInternal(leaves); - - // LUCENENET specific - creating this so that we can call it from the constructor - protected LeafSlice[] GetSlicesInternal(IList leaves) { + // LUCENENET: Added guard clause + if (leaves is null) + throw new ArgumentNullException(nameof(leaves)); + LeafSlice[] slices = new LeafSlice[leaves.Count]; for (int i = 0; i < slices.Length; i++) { @@ -196,15 +226,19 @@ public virtual Document Doc(int docID) /// /// Sugar for .IndexReader.Document(docID, fieldVisitor) /// + /// is null. public virtual void Doc(int docID, StoredFieldVisitor fieldVisitor) { + if (fieldVisitor is null) + throw new ArgumentNullException(nameof(fieldVisitor)); + reader.Document(docID, fieldVisitor); } /// /// Sugar for .IndexReader.Document(docID, fieldsToLoad) /// - public virtual Document Doc(int docID, ISet fieldsToLoad) + public virtual Document Doc(int docID, ISet? fieldsToLoad) { return reader.Document(docID, fieldsToLoad); } @@ -227,7 +261,8 @@ public virtual Similarity Similarity /// /// @lucene.internal - protected virtual Query WrapFilter(Query query, Filter filter) + /// is null. + protected virtual Query WrapFilter(Query query, Filter? filter) { return (filter is null) ? query : new FilteredQuery(query, filter); } @@ -243,7 +278,8 @@ protected virtual Query WrapFilter(Query query, Filter filter) /// /// If a query would exceed /// clauses. - public virtual TopDocs SearchAfter(ScoreDoc after, Query query, int n) + /// is null. + public virtual TopDocs SearchAfter(ScoreDoc? after, Query query, int n) { return Search(CreateNormalizedWeight(query), after, n); } @@ -259,7 +295,8 @@ public virtual TopDocs SearchAfter(ScoreDoc after, Query query, int n) /// /// If a query would exceed /// clauses. - public virtual TopDocs SearchAfter(ScoreDoc after, Query query, Filter filter, int n) + /// is null. + public virtual TopDocs SearchAfter(ScoreDoc? after, Query query, Filter? filter, int n) { return Search(CreateNormalizedWeight(WrapFilter(query, filter)), after, n); } @@ -270,20 +307,21 @@ public virtual TopDocs SearchAfter(ScoreDoc after, Query query, Filter filter, i /// /// If a query would exceed /// clauses. + /// is null. public virtual TopDocs Search(Query query, int n) { - return Search(query, null, n); + return Search(query, filter: null, n); } /// /// Finds the top - /// hits for , applying if non-null. + /// hits for , applying if non-null. /// /// If a query would exceed /// clauses. - public virtual TopDocs Search(Query query, Filter filter, int n) + public virtual TopDocs Search(Query query, Filter? filter, int n) { - return Search(CreateNormalizedWeight(WrapFilter(query, filter)), null, n); + return Search(CreateNormalizedWeight(WrapFilter(query, filter)), after: null, n); } /// @@ -293,11 +331,13 @@ public virtual TopDocs Search(Query query, Filter filter, int n) /// document. /// /// To match documents - /// Ef non-null, used to permit documents to be collected. + /// Ef non-null, used to permit documents to be collected. /// To receive hits /// If a query would exceed /// clauses. - public virtual void Search(Query query, Filter filter, ICollector results) + /// or + /// is null. + public virtual void Search(Query query, Filter? filter, ICollector results) { Search(m_leafContexts, CreateNormalizedWeight(WrapFilter(query, filter)), results); } @@ -309,6 +349,8 @@ public virtual void Search(Query query, Filter filter, ICollector results) /// /// If a query would exceed /// clauses. + /// or + /// is null. public virtual void Search(Query query, ICollector results) { Search(m_leafContexts, CreateNormalizedWeight(query), results); @@ -326,7 +368,9 @@ public virtual void Search(Query query, ICollector results) /// /// If a query would exceed /// clauses. - public virtual TopFieldDocs Search(Query query, Filter filter, int n, Sort sort) + /// or + /// is null. + public virtual TopFieldDocs Search(Query query, Filter? filter, int n, Sort sort) { return Search(CreateNormalizedWeight(WrapFilter(query, filter)), n, sort, false, false); } @@ -345,7 +389,9 @@ public virtual TopFieldDocs Search(Query query, Filter filter, int n, Sort sort) /// /// If a query would exceed /// clauses. - public virtual TopFieldDocs Search(Query query, Filter filter, int n, Sort sort, bool doDocScores, bool doMaxScore) + /// or + /// is null. + public virtual TopFieldDocs Search(Query query, Filter? filter, int n, Sort sort, bool doDocScores, bool doMaxScore) { return Search(CreateNormalizedWeight(WrapFilter(query, filter)), n, sort, doDocScores, doMaxScore); } @@ -361,15 +407,27 @@ public virtual TopFieldDocs Search(Query query, Filter filter, int n, Sort sort, /// /// If a query would exceed /// clauses. - public virtual TopDocs SearchAfter(ScoreDoc after, Query query, Filter filter, int n, Sort sort) + /// or + /// is null. + public virtual TopDocs SearchAfter(ScoreDoc? after, Query query, Filter? filter, int n, Sort sort) + { + FieldDoc? fieldDoc = GetScoreDocAsFieldDocIfNotNull(after); + + return Search(CreateNormalizedWeight(WrapFilter(query, filter)), fieldDoc, n, sort, true, false, false); + } + + private static FieldDoc? GetScoreDocAsFieldDocIfNotNull(ScoreDoc? after) { - if (after != null && !(after is FieldDoc)) + FieldDoc? fieldDoc = null; + // LUCENENET: Simplified type check + if (after is not null) { // TODO: if we fix type safety of TopFieldDocs we can // remove this - throw new ArgumentException("after must be a FieldDoc; got " + after); + fieldDoc = after as FieldDoc ?? throw new ArgumentException($"{nameof(after)} must be a {nameof(FieldDoc)}; got {after}"); } - return Search(CreateNormalizedWeight(WrapFilter(query, filter)), (FieldDoc)after, n, sort, true, false, false); + + return fieldDoc; } /// @@ -379,6 +437,8 @@ public virtual TopDocs SearchAfter(ScoreDoc after, Query query, Filter filter, i /// The object /// The top docs, sorted according to the supplied instance /// if there is a low-level I/O error + /// or + /// is null. public virtual TopFieldDocs Search(Query query, int n, Sort sort) { return Search(CreateNormalizedWeight(query), n, sort, false, false); @@ -395,15 +455,13 @@ public virtual TopFieldDocs Search(Query query, int n, Sort sort) /// /// If a query would exceed /// clauses. - public virtual TopDocs SearchAfter(ScoreDoc after, Query query, int n, Sort sort) + /// or + /// is null. + public virtual TopDocs SearchAfter(ScoreDoc? after, Query query, int n, Sort sort) { - if (after != null && !(after is FieldDoc)) - { - // TODO: if we fix type safety of TopFieldDocs we can - // remove this - throw new ArgumentException("after must be a FieldDoc; got " + after); - } - return Search(CreateNormalizedWeight(query), (FieldDoc)after, n, sort, true, false, false); + var fieldDoc = GetScoreDocAsFieldDocIfNotNull(after); + + return Search(CreateNormalizedWeight(query), fieldDoc, n, sort, true, false, false); } /// @@ -422,15 +480,13 @@ public virtual TopDocs SearchAfter(ScoreDoc after, Query query, int n, Sort sort /// /// If a query would exceed /// clauses. - public virtual TopDocs SearchAfter(ScoreDoc after, Query query, Filter filter, int n, Sort sort, bool doDocScores, bool doMaxScore) + /// or + /// is null. + public virtual TopDocs SearchAfter(ScoreDoc? after, Query query, Filter? filter, int n, Sort sort, bool doDocScores, bool doMaxScore) { - if (after != null && !(after is FieldDoc)) - { - // TODO: if we fix type safety of TopFieldDocs we can - // remove this - throw new ArgumentException("after must be a FieldDoc; got " + after); - } - return Search(CreateNormalizedWeight(WrapFilter(query, filter)), (FieldDoc)after, n, sort, true, doDocScores, doMaxScore); + var fieldDoc = GetScoreDocAsFieldDocIfNotNull(after); + + return Search(CreateNormalizedWeight(WrapFilter(query, filter)), fieldDoc, n, sort, true, doDocScores, doMaxScore); } /// @@ -441,7 +497,8 @@ public virtual TopDocs SearchAfter(ScoreDoc after, Query query, Filter filter, i /// instead. /// If a query would exceed /// clauses. - protected virtual TopDocs Search(Weight weight, ScoreDoc after, int nDocs) + /// is null. + protected virtual TopDocs Search(Weight weight, ScoreDoc? after, int nDocs) { int limit = reader.MaxDoc; if (limit == 0) @@ -460,6 +517,12 @@ protected virtual TopDocs Search(Weight weight, ScoreDoc after, int nDocs) } else { + // LUCENENET: Added guard clauses + if (weight is null) + throw new ArgumentNullException(nameof(weight)); + if (m_leafSlices is null) + throw new InvalidOperationException($"When the constructor is passed a non-null {nameof(TaskScheduler)}, {nameof(m_leafSlices)} must also be set to a non-null value in the constructor."); + HitQueue hq = new HitQueue(nDocs, prePopulate: false); ReentrantLock @lock = new ReentrantLock(); ExecutionHelper runner = new ExecutionHelper(executor); @@ -498,8 +561,14 @@ protected virtual TopDocs Search(Weight weight, ScoreDoc after, int nDocs) /// instead. /// If a query would exceed /// clauses. - protected virtual TopDocs Search(IList leaves, Weight weight, ScoreDoc after, int nDocs) + /// or + /// is null. + protected virtual TopDocs Search(IList leaves, Weight weight, ScoreDoc? after, int nDocs) { + // LUCENENET: Added guard clause + if (weight is null) + throw new ArgumentNullException(nameof(weight)); + // single thread int limit = reader.MaxDoc; if (limit == 0) @@ -524,9 +593,11 @@ protected virtual TopDocs Search(IList leaves, Weight weigh /// /// If a query would exceed /// clauses. + /// or + /// is null. protected virtual TopFieldDocs Search(Weight weight, int nDocs, Sort sort, bool doDocScores, bool doMaxScore) { - return Search(weight, null, nDocs, sort, true, doDocScores, doMaxScore); + return Search(weight, after: null, nDocs, sort, true, doDocScores, doMaxScore); } /// @@ -534,12 +605,12 @@ protected virtual TopFieldDocs Search(Weight weight, int nDocs, Sort sort, bool /// whether or not the fields in the returned instances should /// be set by specifying . /// - protected virtual TopFieldDocs Search(Weight weight, FieldDoc after, int nDocs, Sort sort, bool fillFields, bool doDocScores, bool doMaxScore) + /// or + /// is null. + protected virtual TopFieldDocs Search(Weight weight, FieldDoc? after, int nDocs, Sort sort, bool fillFields, bool doDocScores, bool doMaxScore) { if (sort is null) - { throw new ArgumentNullException(nameof(sort), "Sort must not be null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention) - } int limit = reader.MaxDoc; if (limit == 0) @@ -555,6 +626,12 @@ protected virtual TopFieldDocs Search(Weight weight, FieldDoc after, int nDocs, } else { + // LUCENENET: Added guard clauses + if (weight is null) + throw new ArgumentNullException(nameof(weight)); + if (m_leafSlices is null) + throw new InvalidOperationException($"When the constructor is passed a non-null {nameof(TaskScheduler)}, {nameof(m_leafSlices)} must also be set to a non-null value in the constructor."); + TopFieldCollector topCollector = TopFieldCollector.Create(sort, nDocs, after, fillFields, doDocScores, doMaxScore, false); ReentrantLock @lock = new ReentrantLock(); @@ -587,8 +664,14 @@ protected virtual TopFieldDocs Search(Weight weight, FieldDoc after, int nDocs, /// whether or not the fields in the returned instances should /// be set by specifying . /// - protected virtual TopFieldDocs Search(IList leaves, Weight weight, FieldDoc after, int nDocs, Sort sort, bool fillFields, bool doDocScores, bool doMaxScore) + /// or + /// is null. + protected virtual TopFieldDocs Search(IList leaves, Weight weight, FieldDoc? after, int nDocs, Sort sort, bool fillFields, bool doDocScores, bool doMaxScore) { + // LUCENENET: Added guard clause + if (weight is null) + throw new ArgumentNullException(nameof(weight)); + // single thread int limit = reader.MaxDoc; if (limit == 0) @@ -620,8 +703,18 @@ protected virtual TopFieldDocs Search(IList leaves, Weight /// To receive hits /// If a query would exceed /// clauses. + /// , , + /// or is null. protected virtual void Search(IList leaves, Weight weight, ICollector collector) { + // LUCENENET: Added guard clauses + if (leaves is null) + throw new ArgumentNullException(nameof(leaves)); + if (weight is null) + throw new ArgumentNullException(nameof(weight)); + if (collector is null) + throw new ArgumentNullException(nameof(collector)); + // TODO: should we make this // threaded...? the Collector could be sync'd? // always use single thread: @@ -657,9 +750,13 @@ protected virtual void Search(IList leaves, Weight weight, /// Expert: called to re-write queries into primitive queries. /// If a query would exceed /// clauses. - public virtual Query Rewrite(Query original) + /// is null. + public virtual Query Rewrite(Query query) // LUCENENET: renamed parameter from "original" to "query" so our exception message is consistent across the API { - Query query = original; + // LUCENENET: Added guard clause + if (query is null) + throw new ArgumentNullException(nameof(query)); + for (Query rewrittenQuery = query.Rewrite(reader); rewrittenQuery != query; rewrittenQuery = query.Rewrite(reader)) { query = rewrittenQuery; @@ -676,6 +773,7 @@ public virtual Query Rewrite(Query original) /// Computing an explanation is as expensive as executing the query over the /// entire index. /// + /// is null. public virtual Explanation Explain(Query query, int doc) { return Explain(CreateNormalizedWeight(query), doc); @@ -693,8 +791,13 @@ public virtual Explanation Explain(Query query, int doc) /// Applications should call . /// If a query would exceed /// clauses. + /// is null. protected virtual Explanation Explain(Weight weight, int doc) { + // LUCENENET: Added guard clause + if (weight is null) + throw new ArgumentNullException(nameof(weight)); + int n = ReaderUtil.SubIndex(doc, m_leafContexts); AtomicReaderContext ctx = m_leafContexts[n]; int deBasedDoc = doc - ctx.DocBase; @@ -710,6 +813,7 @@ protected virtual Explanation Explain(Weight weight, int doc) /// /// @lucene.internal /// + /// is null. public virtual Weight CreateNormalizedWeight(Query query) { query = Rewrite(query); @@ -739,12 +843,12 @@ public virtual Weight CreateNormalizedWeight(Query query) private readonly ReentrantLock @lock; private readonly IndexSearcher searcher; private readonly Weight weight; - private readonly ScoreDoc after; + private readonly ScoreDoc? after; private readonly int nDocs; private readonly HitQueue hq; private readonly LeafSlice slice; - public SearcherCallableNoSort(ReentrantLock @lock, IndexSearcher searcher, LeafSlice slice, Weight weight, ScoreDoc after, int nDocs, HitQueue hq) + public SearcherCallableNoSort(ReentrantLock @lock, IndexSearcher searcher, LeafSlice slice, Weight weight, ScoreDoc? after, int nDocs, HitQueue hq) { this.@lock = @lock; this.searcher = searcher; @@ -792,11 +896,11 @@ public TopDocs Call() private readonly TopFieldCollector hq; private readonly Sort sort; private readonly LeafSlice slice; - private readonly FieldDoc after; + private readonly FieldDoc? after; private readonly bool doDocScores; private readonly bool doMaxScore; - public SearcherCallableWithSort(ReentrantLock @lock, IndexSearcher searcher, LeafSlice slice, Weight weight, FieldDoc after, int nDocs, TopFieldCollector hq, Sort sort, bool doDocScores, bool doMaxScore) + public SearcherCallableWithSort(ReentrantLock @lock, IndexSearcher searcher, LeafSlice slice, Weight weight, FieldDoc? after, int nDocs, TopFieldCollector hq, Sort sort, bool doDocScores, bool doMaxScore) { this.@lock = @lock; this.searcher = searcher; @@ -845,6 +949,7 @@ public TopFieldDocs Call() } } +#nullable restore /// /// A helper class that wraps a and provides an /// iterable interface to the completed delegates. @@ -923,7 +1028,8 @@ IEnumerator IEnumerable.GetEnumerator() return this; } } - +#nullable enable + /// /// A class holding a subset of the s leaf contexts to be /// executed within a single thread. @@ -934,9 +1040,15 @@ public class LeafSlice { internal AtomicReaderContext[] Leaves { get; private set; } + /// + /// Initializes a new instance of with + /// the specified . + /// + /// The collection of leaves. + /// is null. public LeafSlice(params AtomicReaderContext[] leaves) { - this.Leaves = leaves; + this.Leaves = leaves ?? throw new ArgumentNullException(nameof(leaves)); // LUCENENET: Added guard clause } } @@ -953,8 +1065,16 @@ public override string ToString() /// /// @lucene.experimental /// + /// or + /// is null. public virtual TermStatistics TermStatistics(Term term, TermContext context) { + // LUCENENET: Added guard clauses + if (term is null) + throw new ArgumentNullException(nameof(term)); + if (context is null) + throw new ArgumentNullException(nameof(context)); + return new TermStatistics(term.Bytes, context.DocFreq, context.TotalTermFreq); } @@ -968,13 +1088,17 @@ public virtual TermStatistics TermStatistics(Term term, TermContext context) /// public virtual CollectionStatistics CollectionStatistics(string field) { + // LUCENENET: Added guard clause + if (field is null) + throw new ArgumentNullException(nameof(field)); + int docCount; long sumTotalTermFreq; long sumDocFreq; - if (Debugging.AssertsEnabled) Debugging.Assert(field != null); + // LUCENENET specific - replaced debug assert check for field being null with above guard clause - Terms terms = MultiFields.GetTerms(reader, field); + Terms? terms = MultiFields.GetTerms(reader, field); if (terms is null) { docCount = 0;