blob: 654e5f2e3868467f7a8169b044bf4524fe544840 [file] [log] [blame]
package org.apache.lucene.search;
/**
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.
*/
import java.io.IOException;
import java.util.Vector;
import org.apache.lucene.index.IndexReader;
/** A Query that matches documents matching boolean combinations of other
queries, typically {@link TermQuery}s or {@link PhraseQuery}s.
*/
public class BooleanQuery extends Query {
/**
* Default value is 1024. Use <code>org.apache.lucene.maxClauseCount</code>
* system property to override.
*/
public static int maxClauseCount =
Integer.parseInt(System.getProperty("org.apache.lucene.maxClauseCount",
"1024"));
/** Thrown when an attempt is made to add more than {@link
* #getMaxClauseCount()} clauses. */
public static class TooManyClauses extends RuntimeException {}
/** 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.*/
public static int getMaxClauseCount() { return maxClauseCount; }
/** Set the maximum number of clauses permitted. */
public static void setMaxClauseCount(int maxClauseCount) {
BooleanQuery.maxClauseCount = maxClauseCount;
}
private Vector clauses = new Vector();
/** Constructs an empty boolean query. */
public BooleanQuery() {}
/** Adds a clause to a boolean query. Clauses may be:
* <ul>
* <li><code>required</code> which means that documents which <i>do not</i>
* match this sub-query will <i>not</i> match the boolean query;
* <li><code>prohibited</code> which means that documents which <i>do</i>
* match this sub-query will <i>not</i> match the boolean query; or
* <li>neither, in which case matched documents are neither prohibited from
* nor required to match the sub-query. However, a document must match at
* least 1 sub-query to match the boolean query.
* </ul>
* It is an error to specify a clause as both <code>required</code> and
* <code>prohibited</code>.
*
* @see #getMaxClauseCount()
*/
public void add(Query query, boolean required, boolean prohibited) {
add(new BooleanClause(query, required, prohibited));
}
/** Adds a clause to a boolean query.
* @see #getMaxClauseCount()
*/
public void add(BooleanClause clause) {
if (clauses.size() >= maxClauseCount)
throw new TooManyClauses();
clauses.addElement(clause);
}
/** Returns the set of clauses in this query. */
public BooleanClause[] getClauses() {
return (BooleanClause[])clauses.toArray(new BooleanClause[0]);
}
private class BooleanWeight implements Weight {
private Searcher searcher;
private Vector weights = new Vector();
public BooleanWeight(Searcher searcher) {
this.searcher = searcher;
for (int i = 0 ; i < clauses.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
weights.add(c.query.createWeight(searcher));
}
}
public Query getQuery() { return BooleanQuery.this; }
public float getValue() { return getBoost(); }
public float sumOfSquaredWeights() throws IOException {
float sum = 0.0f;
for (int i = 0 ; i < weights.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
Weight w = (Weight)weights.elementAt(i);
if (!c.prohibited)
sum += w.sumOfSquaredWeights(); // sum sub weights
}
sum *= getBoost() * getBoost(); // boost each sub-weight
return sum ;
}
public void normalize(float norm) {
norm *= getBoost(); // incorporate boost
for (int i = 0 ; i < weights.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
Weight w = (Weight)weights.elementAt(i);
if (!c.prohibited)
w.normalize(norm);
}
}
public Scorer scorer(IndexReader reader) throws IOException {
// First see if the (faster) ConjunctionScorer will work. This can be
// used when all clauses are required. Also, at this point a
// BooleanScorer cannot be embedded in a ConjunctionScorer, as the hits
// from a BooleanScorer are not always sorted by document number (sigh)
// and hence BooleanScorer cannot implement skipTo() correctly, which is
// required by ConjunctionScorer.
boolean allRequired = true;
boolean noneBoolean = true;
for (int i = 0 ; i < weights.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
if (!c.required)
allRequired = false;
if (c.query instanceof BooleanQuery)
noneBoolean = false;
}
if (allRequired && noneBoolean) { // ConjunctionScorer is okay
ConjunctionScorer result =
new ConjunctionScorer(getSimilarity(searcher));
for (int i = 0 ; i < weights.size(); i++) {
Weight w = (Weight)weights.elementAt(i);
Scorer subScorer = w.scorer(reader);
if (subScorer == null)
return null;
result.add(subScorer);
}
return result;
}
// Use good-old BooleanScorer instead.
BooleanScorer result = new BooleanScorer(getSimilarity(searcher));
for (int i = 0 ; i < weights.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
Weight w = (Weight)weights.elementAt(i);
Scorer subScorer = w.scorer(reader);
if (subScorer != null)
result.add(subScorer, c.required, c.prohibited);
else if (c.required)
return null;
}
return result;
}
public Explanation explain(IndexReader reader, int doc)
throws IOException {
Explanation sumExpl = new Explanation();
sumExpl.setDescription("sum of:");
int coord = 0;
int maxCoord = 0;
float sum = 0.0f;
for (int i = 0 ; i < weights.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
Weight w = (Weight)weights.elementAt(i);
Explanation e = w.explain(reader, doc);
if (!c.prohibited) maxCoord++;
if (e.getValue() > 0) {
if (!c.prohibited) {
sumExpl.addDetail(e);
sum += e.getValue();
coord++;
} else {
return new Explanation(0.0f, "match prohibited");
}
} else if (c.required) {
return new Explanation(0.0f, "match required");
}
}
sumExpl.setValue(sum);
if (coord == 1) // only one clause matched
sumExpl = sumExpl.getDetails()[0]; // eliminate wrapper
float coordFactor = getSimilarity(searcher).coord(coord, maxCoord);
if (coordFactor == 1.0f) // coord is no-op
return sumExpl; // eliminate wrapper
else {
Explanation result = new Explanation();
result.setDescription("product of:");
result.addDetail(sumExpl);
result.addDetail(new Explanation(coordFactor,
"coord("+coord+"/"+maxCoord+")"));
result.setValue(sum*coordFactor);
return result;
}
}
}
protected Weight createWeight(Searcher searcher) {
return new BooleanWeight(searcher);
}
public Query rewrite(IndexReader reader) throws IOException {
if (clauses.size() == 1) { // optimize 1-clause queries
BooleanClause c = (BooleanClause)clauses.elementAt(0);
if (!c.prohibited) { // just return clause
Query query = c.query.rewrite(reader); // rewrite first
if (getBoost() != 1.0f) { // incorporate boost
if (query == c.query) // 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.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
Query query = c.query.rewrite(reader);
if (query != c.query) { // clause rewrote: must clone
if (clone == null)
clone = (BooleanQuery)this.clone();
clone.clauses.setElementAt
(new BooleanClause(query, c.required, c.prohibited), i);
}
}
if (clone != null) {
return clone; // some clauses rewrote
} else
return this; // no clauses rewrote
}
public Object clone() {
BooleanQuery clone = (BooleanQuery)super.clone();
clone.clauses = (Vector)this.clauses.clone();
return clone;
}
/** Prints a user-readable version of this query. */
public String toString(String field) {
StringBuffer buffer = new StringBuffer();
if (getBoost() != 1.0) {
buffer.append("(");
}
for (int i = 0 ; i < clauses.size(); i++) {
BooleanClause c = (BooleanClause)clauses.elementAt(i);
if (c.prohibited)
buffer.append("-");
else if (c.required)
buffer.append("+");
Query subQuery = c.query;
if (subQuery instanceof BooleanQuery) { // wrap sub-bools in parens
buffer.append("(");
buffer.append(c.query.toString(field));
buffer.append(")");
} else
buffer.append(c.query.toString(field));
if (i != clauses.size()-1)
buffer.append(" ");
}
if (getBoost() != 1.0) {
buffer.append(")^");
buffer.append(getBoost());
}
return buffer.toString();
}
/** Returns true iff <code>o</code> is equal to this. */
public boolean equals(Object o) {
if (!(o instanceof BooleanQuery))
return false;
BooleanQuery other = (BooleanQuery)o;
return (this.getBoost() == other.getBoost())
&& this.clauses.equals(other.clauses);
}
/** Returns a hash code value for this object.*/
public int hashCode() {
return Float.floatToIntBits(getBoost()) ^ clauses.hashCode();
}
}