/* * 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; using Lucene.Net.Index; using Lucene.Net.Support; using IndexReader = Lucene.Net.Index.IndexReader; using ToStringUtils = Lucene.Net.Util.ToStringUtils; using Occur = Lucene.Net.Search.Occur; namespace Lucene.Net.Search { /// A Query that matches documents matching boolean combinations of other /// queries, e.g. s, s or other /// BooleanQuerys. /// [Serializable] public class BooleanQuery : Query, System.Collections.Generic.IEnumerable, System.ICloneable { [Serializable] private class AnonymousClassSimilarityDelegator:SimilarityDelegator { private void InitBlock(BooleanQuery enclosingInstance) { this.enclosingInstance = enclosingInstance; } private BooleanQuery enclosingInstance; public BooleanQuery Enclosing_Instance { get { return enclosingInstance; } } internal AnonymousClassSimilarityDelegator(BooleanQuery enclosingInstance, Lucene.Net.Search.Similarity Param1):base(Param1) { InitBlock(enclosingInstance); } public override float Coord(int overlap, int maxOverlap) { return 1.0f; } } private static int _maxClauses = 1024; /// Thrown when an attempt is made to add more than /// clauses. This typically happens if /// a PrefixQuery, FuzzyQuery, WildcardQuery, or TermRangeQuery /// is expanded to many terms during search. /// [Serializable] public class TooManyClauses:System.SystemException { public override System.String Message { get { return "maxClauseCount is set to " + Lucene.Net.Search.BooleanQuery._maxClauses; } } } /// Gets or sets the maximum number of clauses permitted, 1024 by default. /// Attempts to add more than the permitted number of clauses cause /// to be thrown. /// public static int MaxClauseCount { get { return _maxClauses; } set { if (value < 1) throw new ArgumentException("maxClauseCount must be >= 1"); _maxClauses = value; } } private EquatableList clauses = new EquatableList(); private bool disableCoord; /// Constructs an empty boolean query. public BooleanQuery() { } /// Constructs an empty boolean query. /// /// may be disabled in scoring, as /// appropriate. For example, this score factor does not make sense for most /// automatically generated queries, like and ///. /// /// /// disables in scoring. /// public BooleanQuery(bool disableCoord) { this.disableCoord = disableCoord; } /// Returns true iff is disabled in /// scoring for this query instance. /// /// /// public virtual bool IsCoordDisabled() { return disableCoord; } // Implement coord disabling. // Inherit javadoc. public override Similarity GetSimilarity(Searcher searcher) { Similarity result = base.GetSimilarity(searcher); if (disableCoord) { // disable coord as requested result = new AnonymousClassSimilarityDelegator(this, result); } return result; } protected internal int minNrShouldMatch = 0; /// /// Specifies a minimum number of the optional BooleanClauses /// which must be satisfied. /// /// By default no optional clauses are necessary for a match /// (unless there are no required clauses). If this method is used, /// then the specified number of clauses is required. /// /// /// Use of this method is totally independent of specifying that /// any specific clauses are required (or prohibited). This number will /// only be compared against the number of matching optional clauses. /// /// public virtual int MinimumNumberShouldMatch { set { this.minNrShouldMatch = value; } get { return minNrShouldMatch; } } /// Adds a clause to a boolean query. /// /// /// TooManyClauses if the new number of clauses exceeds the maximum clause number /// /// public virtual void Add(Query query, Occur occur) { Add(new BooleanClause(query, occur)); } /// Adds a clause to a boolean query. /// TooManyClauses if the new number of clauses exceeds the maximum clause number /// /// public virtual void Add(BooleanClause clause) { if (clauses.Count >= _maxClauses) throw new TooManyClauses(); clauses.Add(clause); } /// Returns the set of clauses in this query. public virtual BooleanClause[] GetClauses() { return clauses.ToArray(); } /// Returns the list of clauses in this query. public virtual System.Collections.Generic.List Clauses { get { return clauses; } } /// /// Returns an iterator on the clauses in this query. /// /// public System.Collections.Generic.IEnumerator GetEnumerator() { return clauses.GetEnumerator(); } /// Expert: the Weight for BooleanQuery, used to /// normalize, score and explain these queries. /// ///

NOTE: this API and implementation is subject to /// change suddenly in the next release.

///

[Serializable] protected internal class BooleanWeight:Weight { private void InitBlock(BooleanQuery enclosingInstance) { this.enclosingInstance = enclosingInstance; } private BooleanQuery enclosingInstance; public BooleanQuery Enclosing_Instance { get { return enclosingInstance; } } /// The Similarity implementation. protected internal Similarity similarity; protected internal System.Collections.Generic.List weights; public BooleanWeight(BooleanQuery enclosingInstance, Searcher searcher) { InitBlock(enclosingInstance); this.similarity = Enclosing_Instance.GetSimilarity(searcher); weights = new System.Collections.Generic.List(Enclosing_Instance.clauses.Count); for (int i = 0; i < Enclosing_Instance.clauses.Count; i++) { weights.Add(Enclosing_Instance.clauses[i].Query.CreateWeight(searcher)); } } public override Query Query { get { return Enclosing_Instance; } } public override float Value { get { return Enclosing_Instance.Boost; } } public override float GetSumOfSquaredWeights() { float sum = 0.0f; for (int i = 0; i < weights.Count; i++) { // call sumOfSquaredWeights for all clauses in case of side effects float s = weights[i].GetSumOfSquaredWeights(); // sum sub weights if (!Enclosing_Instance.clauses[i].IsProhibited) // only add to sum for non-prohibited clauses sum += s; } sum *= Enclosing_Instance.Boost*Enclosing_Instance.Boost; // boost each sub-weight return sum; } public override void Normalize(float norm) { norm *= Enclosing_Instance.Boost; // incorporate boost foreach (Weight w in weights) { // normalize all clauses, (even if prohibited in case of side affects) w.Normalize(norm); } } public override Explanation Explain(IndexReader reader, int doc) { int minShouldMatch = Enclosing_Instance.MinimumNumberShouldMatch; ComplexExplanation sumExpl = new ComplexExplanation(); sumExpl.Description = "sum of:"; int coord = 0; int maxCoord = 0; float sum = 0.0f; bool fail = false; int shouldMatchCount = 0; System.Collections.Generic.IEnumerator cIter = Enclosing_Instance.clauses.GetEnumerator(); for (System.Collections.Generic.IEnumerator wIter = weights.GetEnumerator(); wIter.MoveNext(); ) { cIter.MoveNext(); Weight w = wIter.Current; BooleanClause c = cIter.Current; if (w.Scorer(reader, true, true) == null) { continue; } Explanation e = w.Explain(reader, doc); if (!c.IsProhibited) maxCoord++; if (e.IsMatch) { if (!c.IsProhibited) { sumExpl.AddDetail(e); sum += e.Value; coord++; } else { Explanation r = new Explanation(0.0f, "match on prohibited clause (" + c.Query.ToString() + ")"); r.AddDetail(e); sumExpl.AddDetail(r); fail = true; } if (c.Occur == Occur.SHOULD) shouldMatchCount++; } else if (c.IsRequired) { Explanation r = new Explanation(0.0f, "no match on required clause (" + c.Query.ToString() + ")"); r.AddDetail(e); sumExpl.AddDetail(r); fail = true; } } if (fail) { System.Boolean tempAux = false; sumExpl.Match = tempAux; sumExpl.Value = 0.0f; sumExpl.Description = "Failure to meet condition(s) of required/prohibited clause(s)"; return sumExpl; } else if (shouldMatchCount < minShouldMatch) { System.Boolean tempAux2 = false; sumExpl.Match = tempAux2; sumExpl.Value = 0.0f; sumExpl.Description = "Failure to match minimum number " + "of optional clauses: " + minShouldMatch; return sumExpl; } sumExpl.Match = 0 < coord?true:false; sumExpl.Value = sum; float coordFactor = similarity.Coord(coord, maxCoord); if (coordFactor == 1.0f) // coord is no-op return sumExpl; // eliminate wrapper else { ComplexExplanation result = new ComplexExplanation(sumExpl.IsMatch, sum * coordFactor, "product of:"); result.AddDetail(sumExpl); result.AddDetail(new Explanation(coordFactor, "coord(" + coord + "/" + maxCoord + ")")); return result; } } public override Scorer Scorer(IndexReader reader, bool scoreDocsInOrder, bool topScorer) { var required = new System.Collections.Generic.List(); var prohibited = new System.Collections.Generic.List(); var optional = new System.Collections.Generic.List(); System.Collections.Generic.IEnumerator cIter = Enclosing_Instance.clauses.GetEnumerator(); foreach (Weight w in weights) { cIter.MoveNext(); BooleanClause c = (BooleanClause) cIter.Current; Scorer subScorer = w.Scorer(reader, true, false); if (subScorer == null) { if (c.IsRequired) { return null; } } else if (c.IsRequired) { required.Add(subScorer); } else if (c.IsProhibited) { prohibited.Add(subScorer); } else { optional.Add(subScorer); } } // Check if we can return a BooleanScorer if (!scoreDocsInOrder && topScorer && required.Count == 0 && prohibited.Count < 32) { return new BooleanScorer(similarity, Enclosing_Instance.minNrShouldMatch, optional, prohibited); } if (required.Count == 0 && optional.Count == 0) { // no required and optional clauses. return null; } else if (optional.Count < Enclosing_Instance.minNrShouldMatch) { // either >1 req scorer, or there are 0 req scorers and at least 1 // optional scorer. Therefore if there are not enough optional scorers // no documents will be matched by the query return null; } // Return a BooleanScorer2 return new BooleanScorer2(similarity, Enclosing_Instance.minNrShouldMatch, required, prohibited, optional); } public override bool GetScoresDocsOutOfOrder() { int numProhibited = 0; foreach (BooleanClause c in Enclosing_Instance.clauses) { if (c.IsRequired) { return false; // BS2 (in-order) will be used by scorer() } else if (c.IsProhibited) { ++numProhibited; } } if (numProhibited > 32) { // cannot use BS return false; } // scorer() will return an out-of-order scorer if requested. return true; } } public override Weight CreateWeight(Searcher searcher) { return new BooleanWeight(this, searcher); } public override Query Rewrite(IndexReader reader) { if (minNrShouldMatch == 0 && clauses.Count == 1) { // optimize 1-clause queries BooleanClause c = clauses[0]; if (!c.IsProhibited) { // just return clause Query query = c.Query.Rewrite(reader); // rewrite first if (Boost != 1.0f) { // incorporate boost if (query == c.Query) // if rewrite was no-op query = (Query) query.Clone(); // then clone before boost query.Boost = Boost * query.Boost; } return query; } } BooleanQuery clone = null; // recursively rewrite for (int i = 0; i < clauses.Count; i++) { BooleanClause c = clauses[i]; Query query = c.Query.Rewrite(reader); if (query != c.Query) { // clause rewrote: must clone if (clone == null) clone = (BooleanQuery) this.Clone(); clone.clauses[i] = new BooleanClause(query, c.Occur); } } if (clone != null) { return clone; // some clauses rewrote } else return this; // no clauses rewrote } // inherit javadoc public override void ExtractTerms(System.Collections.Generic.ISet terms) { foreach(BooleanClause clause in clauses) { clause.Query.ExtractTerms(terms); } } public override System.Object Clone() { BooleanQuery clone = (BooleanQuery) base.Clone(); clone.clauses = (EquatableList) this.clauses.Clone(); return clone; } /// Prints a user-readable version of this query. public override System.String ToString(System.String field) { System.Text.StringBuilder buffer = new System.Text.StringBuilder(); bool needParens = (Boost != 1.0) || (MinimumNumberShouldMatch > 0); if (needParens) { buffer.Append("("); } for (int i = 0; i < clauses.Count; i++) { BooleanClause c = clauses[i]; if (c.IsProhibited) buffer.Append("-"); else if (c.IsRequired) buffer.Append("+"); Query subQuery = c.Query; if (subQuery != null) { if (subQuery is BooleanQuery) { // wrap sub-bools in parens buffer.Append("("); buffer.Append(subQuery.ToString(field)); buffer.Append(")"); } else { buffer.Append(subQuery.ToString(field)); } } else { buffer.Append("null"); } if (i != clauses.Count - 1) buffer.Append(" "); } if (needParens) { buffer.Append(")"); } if (MinimumNumberShouldMatch > 0) { buffer.Append('~'); buffer.Append(MinimumNumberShouldMatch); } if (Boost != 1.0f) { buffer.Append(ToStringUtils.Boost(Boost)); } return buffer.ToString(); } /// Returns true iff o is equal to this. public override bool Equals(System.Object o) { if (!(o is BooleanQuery)) return false; BooleanQuery other = (BooleanQuery)o; return (this.Boost == other.Boost) && this.clauses.Equals(other.clauses) && this.MinimumNumberShouldMatch == other.MinimumNumberShouldMatch && this.disableCoord == other.disableCoord; } /// Returns a hash code value for this object. public override int GetHashCode() { return BitConverter.ToInt32(BitConverter.GetBytes(Boost), 0) ^ clauses.GetHashCode() + MinimumNumberShouldMatch + (disableCoord ? 17 : 0); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }