/*
* 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 IndexReader = Lucene.Net.Index.IndexReader;
using ToStringUtils = Lucene.Net.Util.ToStringUtils;
using Occur = Lucene.Net.Search.BooleanClause.Occur;
namespace Lucene.Net.Search
{
/// <summary>A Query that matches documents matching boolean combinations of other
/// queries, e.g. {@link TermQuery}s, {@link PhraseQuery}s or other
/// BooleanQuerys.
/// </summary>
[Serializable]
public class BooleanQuery : Query, 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 maxClauseCount = 1024;
/// <summary>Thrown when an attempt is made to add more than {@link
/// #GetMaxClauseCount()} clauses. This typically happens if
/// a PrefixQuery, FuzzyQuery, WildcardQuery, or RangeQuery
/// is expanded to many terms during search.
/// </summary>
[Serializable]
public class TooManyClauses : System.SystemException
{
public override System.String Message
{
get
{
return "maxClauseCount is set to " + Lucene.Net.Search.BooleanQuery.maxClauseCount;
}
}
public TooManyClauses()
{
}
}
/// <summary>Return the maximum number of clauses permitted, 1024 by default.
/// Attempts to add more than the permitted number of clauses cause {@link
/// TooManyClauses} to be thrown.
/// </summary>
/// <seealso cref="#SetMaxClauseCount(int)">
/// </seealso>
public static int GetMaxClauseCount()
{
return maxClauseCount;
}
/// <summary>Set the maximum number of clauses permitted per BooleanQuery.
/// Default value is 1024.
/// <p>TermQuery clauses are generated from for example prefix queries and
/// fuzzy queries. Each TermQuery needs some buffer space during search,
/// so this parameter indirectly controls the maximum buffer requirements for
/// query search.
/// <p>When this parameter becomes a bottleneck for a Query one can use a
/// Filter. For example instead of a {@link RangeQuery} one can use a
/// {@link RangeFilter}.
/// <p>Normally the buffers are allocated by the JVM. When using for example
/// {@link Lucene.Net.Store.MMapDirectory} the buffering is left to
/// the operating system.
/// </summary>
public static void SetMaxClauseCount(int maxClauseCount)
{
if (maxClauseCount < 1)
throw new System.ArgumentException("maxClauseCount must be >= 1");
BooleanQuery.maxClauseCount = maxClauseCount;
}
private System.Collections.ArrayList clauses = new System.Collections.ArrayList();
private bool disableCoord;
/// <summary>Constructs an empty boolean query. </summary>
public BooleanQuery()
{
}
/// <summary>Constructs an empty boolean query.
///
/// {@link Similarity#Coord(int,int)} may be disabled in scoring, as
/// appropriate. For example, this score factor does not make sense for most
/// automatically generated queries, like {@link WildcardQuery} and {@link
/// FuzzyQuery}.
///
/// </summary>
/// <param name="disableCoord">disables {@link Similarity#Coord(int,int)} in scoring.
/// </param>
public BooleanQuery(bool disableCoord)
{
this.disableCoord = disableCoord;
}
/// <summary>Returns true iff {@link Similarity#Coord(int,int)} is disabled in
/// scoring for this query instance.
/// </summary>
/// <seealso cref="#BooleanQuery(boolean)">
/// </seealso>
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;
}
/// <summary> Specifies a minimum number of the optional BooleanClauses
/// which must be satisfied.
///
/// <p>
/// 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.
/// </p>
/// <p>
/// 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.
/// </p>
/// <p>
/// EXPERT NOTE: Using this method may force collecting docs in order,
/// regardless of whether setAllowDocsOutOfOrder(true) has been called.
/// </p>
///
/// </summary>
/// <param name="min">the number of optional clauses that must match
/// </param>
/// <seealso cref="setAllowDocsOutOfOrder">
/// </seealso>
public virtual void SetMinimumNumberShouldMatch(int min)
{
this.minNrShouldMatch = min;
}
protected internal int minNrShouldMatch = 0;
/// <summary> Gets the minimum number of the optional BooleanClauses
/// which must be satisifed.
/// </summary>
public virtual int GetMinimumNumberShouldMatch()
{
return minNrShouldMatch;
}
/// <summary>Adds a clause to a boolean query.
///
/// </summary>
/// <throws> TooManyClauses if the new number of clauses exceeds the maximum clause number </throws>
/// <seealso cref="#GetMaxClauseCount()">
/// </seealso>
public virtual void Add(Query query, BooleanClause.Occur occur)
{
Add(new BooleanClause(query, occur));
}
/// <summary>Adds a clause to a boolean query.</summary>
/// <throws> TooManyClauses if the new number of clauses exceeds the maximum clause number </throws>
/// <seealso cref="#GetMaxClauseCount()">
/// </seealso>
public virtual void Add(BooleanClause clause)
{
if (clauses.Count >= maxClauseCount)
throw new TooManyClauses();
clauses.Add(clause);
}
/// <summary>Returns the set of clauses in this query. </summary>
public virtual BooleanClause[] GetClauses()
{
return (BooleanClause[]) clauses.ToArray(typeof(BooleanClause));
}
/// <summary>Returns the list of clauses in this query. </summary>
public virtual System.Collections.IList Clauses()
{
return clauses;
}
[Serializable]
private class BooleanWeight : Weight
{
private void InitBlock(BooleanQuery enclosingInstance)
{
this.enclosingInstance = enclosingInstance;
}
private BooleanQuery enclosingInstance;
public BooleanQuery Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
protected internal Similarity similarity;
protected internal System.Collections.ArrayList weights = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
public BooleanWeight(BooleanQuery enclosingInstance, Searcher searcher)
{
InitBlock(enclosingInstance);
this.similarity = Enclosing_Instance.GetSimilarity(searcher);
for (int i = 0; i < Enclosing_Instance.clauses.Count; i++)
{
BooleanClause c = (BooleanClause) Enclosing_Instance.clauses[i];
weights.Add(c.GetQuery().CreateWeight(searcher));
}
}
public virtual Query GetQuery()
{
return Enclosing_Instance;
}
public virtual float GetValue()
{
return Enclosing_Instance.GetBoost();
}
public virtual float SumOfSquaredWeights()
{
float sum = 0.0f;
for (int i = 0; i < weights.Count; i++)
{
BooleanClause c = (BooleanClause) Enclosing_Instance.clauses[i];
Weight w = (Weight) weights[i];
// call sumOfSquaredWeights for all clauses in case of side effects
float s = w.SumOfSquaredWeights(); // sum sub k
if (!c.IsProhibited())
// only add to sum for non-prohibited clauses
sum += s;
}
sum *= Enclosing_Instance.GetBoost() * Enclosing_Instance.GetBoost(); // boost each sub-weight
return sum;
}
public virtual void Normalize(float norm)
{
norm *= Enclosing_Instance.GetBoost(); // incorporate boost
for (int i = 0; i < weights.Count; i++)
{
Weight w = (Weight) weights[i];
// normalize all clauses, (even if prohibited in case of side affects)
w.Normalize(norm);
}
}
/// <returns> Returns BooleanScorer2 that uses and provides skipTo(),
/// and scores documents in document number order.
/// </returns>
public virtual Scorer Scorer(IndexReader reader)
{
BooleanScorer2 result = new BooleanScorer2(similarity, Enclosing_Instance.minNrShouldMatch, Lucene.Net.Search.BooleanQuery.allowDocsOutOfOrder);
for (int i = 0; i < weights.Count; i++)
{
BooleanClause c = (BooleanClause) Enclosing_Instance.clauses[i];
Weight w = (Weight) weights[i];
Scorer subScorer = w.Scorer(reader);
if (subScorer != null)
result.Add(subScorer, c.IsRequired(), c.IsProhibited());
else if (c.IsRequired())
return null;
}
return result;
}
public virtual Explanation Explain(IndexReader reader, int doc)
{
int minShouldMatch = Enclosing_Instance.GetMinimumNumberShouldMatch();
ComplexExplanation sumExpl = new ComplexExplanation();
sumExpl.SetDescription("sum of:");
int coord = 0;
int maxCoord = 0;
float sum = 0.0f;
bool fail = false;
int shouldMatchCount = 0;
for (int i = 0; i < weights.Count; i++)
{
BooleanClause c = (BooleanClause) Enclosing_Instance.clauses[i];
Weight w = (Weight) weights[i];
Explanation e = w.Explain(reader, doc);
if (!c.IsProhibited())
maxCoord++;
if (e.IsMatch())
{
if (!c.IsProhibited())
{
sumExpl.AddDetail(e);
sum += e.GetValue();
coord++;
}
else
{
Explanation r = new Explanation(0.0f, "match on prohibited clause (" + c.GetQuery().ToString() + ")");
r.AddDetail(e);
sumExpl.AddDetail(r);
fail = true;
}
if (c.GetOccur().Equals(Occur.SHOULD))
shouldMatchCount++;
}
else if (c.IsRequired())
{
Explanation r = new Explanation(0.0f, "no match on required clause (" + c.GetQuery().ToString() + ")");
r.AddDetail(e);
sumExpl.AddDetail(r);
fail = true;
}
}
if (fail)
{
System.Boolean tempAux = false;
sumExpl.SetMatch(tempAux);
sumExpl.SetValue(0.0f);
sumExpl.SetDescription("Failure to meet condition(s) of required/prohibited clause(s)");
return sumExpl;
}
else if (shouldMatchCount < minShouldMatch)
{
System.Boolean tempAux2 = false;
sumExpl.SetMatch(tempAux2);
sumExpl.SetValue(0.0f);
sumExpl.SetDescription("Failure to match minimum number " + "of optional clauses: " + minShouldMatch);
return sumExpl;
}
sumExpl.SetMatch(0 < coord ? true : false);
sumExpl.SetValue(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;
}
}
}
/// <summary>Whether hit docs may be collected out of docid order. </summary>
private static bool allowDocsOutOfOrder = false;
/// <summary> Expert: Indicates whether hit docs may be collected out of docid
/// order.
///
/// <p>
/// Background: although the contract of the Scorer class requires that
/// documents be iterated in order of doc id, this was not true in early
/// versions of Lucene. Many pieces of functionality in the current
/// Lucene code base have undefined behavior if this contract is not
/// upheld, but in some specific simple cases may be faster. (For
/// example: disjunction queries with less than 32 prohibited clauses;
/// This setting has no effect for other queries.)
/// </p>
///
/// <p>
/// Specifics: By setting this option to true, calls to
/// {@link HitCollector#Collect(int,float)} might be
/// invoked first for docid N and only later for docid N-1.
/// Being static, this setting is system wide.
/// </p>
/// </summary>
public static void SetAllowDocsOutOfOrder(bool allow)
{
allowDocsOutOfOrder = allow;
}
/// <summary> Whether hit docs may be collected out of docid order.</summary>
/// <seealso cref="SetAllowDocsOutOfOrder(boolean)">
/// </seealso>
public static bool GetAllowDocsOutOfOrder()
{
return allowDocsOutOfOrder;
}
/// <deprecated> Use {@link #SetAllowDocsOutOfOrder(boolean)} instead.
/// </deprecated>
public static void SetUseScorer14(bool use14)
{
SetAllowDocsOutOfOrder(use14);
}
/// <deprecated> Use {@link #GetAllowDocsOutOfOrder()} instead.
/// </deprecated>
public static bool GetUseScorer14()
{
return GetAllowDocsOutOfOrder();
}
protected internal override Weight CreateWeight(Searcher searcher)
{
return new BooleanWeight(this, searcher);
}
public override Query Rewrite(IndexReader reader)
{
if (clauses.Count == 1)
{
// optimize 1-clause queries
BooleanClause c = (BooleanClause) clauses[0];
if (!c.IsProhibited())
{
// just return clause
Query query = c.GetQuery().Rewrite(reader); // rewrite first
if (GetBoost() != 1.0f)
{
// incorporate boost
if (query == c.GetQuery())
// if rewrite was no-op
query = (Query) query.Clone(); // then clone before boost
query.SetBoost(GetBoost() * query.GetBoost());
}
return query;
}
}
BooleanQuery clone = null; // recursively rewrite
for (int i = 0; i < clauses.Count; i++)
{
BooleanClause c = (BooleanClause) clauses[i];
Query query = c.GetQuery().Rewrite(reader);
if (query != c.GetQuery())
{
// clause rewrote: must clone
if (clone == null)
clone = (BooleanQuery) this.Clone();
clone.clauses[i] = new BooleanClause(query, c.GetOccur());
}
}
if (clone != null)
{
return clone; // some clauses rewrote
}
else
return this; // no clauses rewrote
}
// inherit javadoc
public override void ExtractTerms(System.Collections.Hashtable terms)
{
for (System.Collections.IEnumerator i = clauses.GetEnumerator(); i.MoveNext(); )
{
BooleanClause clause = (BooleanClause) i.Current;
clause.GetQuery().ExtractTerms(terms);
}
}
public override System.Object Clone()
{
BooleanQuery clone = (BooleanQuery) base.Clone();
clone.clauses = (System.Collections.ArrayList) this.clauses.Clone();
return clone;
}
/// <summary>Prints a user-readable version of this query. </summary>
public override System.String ToString(System.String field)
{
System.Text.StringBuilder buffer = new System.Text.StringBuilder();
bool needParens = (GetBoost() != 1.0) || (GetMinimumNumberShouldMatch() > 0);
if (needParens)
{
buffer.Append("(");
}
for (int i = 0; i < clauses.Count; i++)
{
BooleanClause c = (BooleanClause) clauses[i];
if (c.IsProhibited())
buffer.Append("-");
else if (c.IsRequired())
buffer.Append("+");
Query subQuery = c.GetQuery();
if (subQuery is BooleanQuery)
{
// wrap sub-bools in parens
buffer.Append("(");
buffer.Append(c.GetQuery().ToString(field));
buffer.Append(")");
}
else
buffer.Append(c.GetQuery().ToString(field));
if (i != clauses.Count - 1)
buffer.Append(" ");
}
if (needParens)
{
buffer.Append(")");
}
if (GetMinimumNumberShouldMatch() > 0)
{
buffer.Append('~');
buffer.Append(GetMinimumNumberShouldMatch());
}
if (GetBoost() != 1.0f)
{
buffer.Append(ToStringUtils.Boost(GetBoost()));
}
return buffer.ToString().Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, ".");
}
/// <summary>Returns true iff <code>o</code> is equal to this. </summary>
public override bool Equals(System.Object o)
{
if (!(o is BooleanQuery))
return false;
BooleanQuery other = (BooleanQuery) o;
if (this.GetBoost() != other.GetBoost())
return false;
if (this.clauses.Count != other.clauses.Count)
return false;
for (int i = 0; i < this.clauses.Count; i++)
{
if (this.clauses[i].Equals(other.clauses[i]) == false)
return false;
}
return this.GetMinimumNumberShouldMatch() == other.GetMinimumNumberShouldMatch();
}
/// <summary>Returns a hash code value for this object.</summary>
public override int GetHashCode()
{
int hashCode = 0;
for (int i = 0; i < clauses.Count; i++)
{
hashCode += clauses[i].GetHashCode();
}
return BitConverter.ToInt32(BitConverter.GetBytes(GetBoost()), 0) ^ hashCode + GetMinimumNumberShouldMatch();
}
}
}