+++ /dev/null
-package org.apache.lucene.search;
-
-/**
- * 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.
- */
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.util.ToStringUtils;
-import org.apache.lucene.search.BooleanClause.Occur;
-
-import java.io.IOException;
-import java.util.*;
-
-/** A Query that matches documents matching boolean combinations of other
- * queries, e.g. {@link TermQuery}s, {@link PhraseQuery}s or other
- * BooleanQuerys.
- */
-public class BooleanQuery extends Query implements Iterable<BooleanClause> {
-
- private static int maxClauseCount = 1024;
-
- /** Thrown when an attempt is made to add more than {@link
- * #getMaxClauseCount()} clauses. This typically happens if
- * a PrefixQuery, FuzzyQuery, WildcardQuery, or TermRangeQuery
- * is expanded to many terms during search.
- */
- public static class TooManyClauses extends RuntimeException {
- public TooManyClauses() {
- super("maxClauseCount is set to " + maxClauseCount);
- }
- }
-
- /** 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.
- * @see #setMaxClauseCount(int)
- */
- public static int getMaxClauseCount() { return maxClauseCount; }
-
- /**
- * Set the maximum number of clauses permitted per BooleanQuery.
- * Default value is 1024.
- */
- public static void setMaxClauseCount(int maxClauseCount) {
- if (maxClauseCount < 1)
- throw new IllegalArgumentException("maxClauseCount must be >= 1");
- BooleanQuery.maxClauseCount = maxClauseCount;
- }
-
- private ArrayList<BooleanClause> clauses = new ArrayList<BooleanClause>();
- private final boolean disableCoord;
-
- /** Constructs an empty boolean query. */
- public BooleanQuery() {
- disableCoord = false;
- }
-
- /** 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}.
- *
- * @param disableCoord disables {@link Similarity#coord(int,int)} in scoring.
- */
- public BooleanQuery(boolean disableCoord) {
- this.disableCoord = disableCoord;
- }
-
- /** Returns true iff {@link Similarity#coord(int,int)} is disabled in
- * scoring for this query instance.
- * @see #BooleanQuery(boolean)
- */
- public boolean isCoordDisabled() { return disableCoord; }
-
- /**
- * 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>
- *
- * @param min the number of optional clauses that must match
- */
- public void setMinimumNumberShouldMatch(int min) {
- this.minNrShouldMatch = min;
- }
- protected int minNrShouldMatch = 0;
-
- /**
- * Gets the minimum number of the optional BooleanClauses
- * which must be satisfied.
- */
- public int getMinimumNumberShouldMatch() {
- return minNrShouldMatch;
- }
-
- /** Adds a clause to a boolean query.
- *
- * @throws TooManyClauses if the new number of clauses exceeds the maximum clause number
- * @see #getMaxClauseCount()
- */
- public void add(Query query, BooleanClause.Occur occur) {
- add(new BooleanClause(query, occur));
- }
-
- /** Adds a clause to a boolean query.
- * @throws TooManyClauses if the new number of clauses exceeds the maximum clause number
- * @see #getMaxClauseCount()
- */
- public void add(BooleanClause clause) {
- if (clauses.size() >= maxClauseCount)
- throw new TooManyClauses();
-
- clauses.add(clause);
- }
-
- /** Returns the set of clauses in this query. */
- public BooleanClause[] getClauses() {
- return clauses.toArray(new BooleanClause[clauses.size()]);
- }
-
- /** Returns the list of clauses in this query. */
- public List<BooleanClause> clauses() { return clauses; }
-
- /** Returns an iterator on the clauses in this query. It implements the {@link Iterable} interface to
- * make it possible to do:
- * <pre>for (BooleanClause clause : booleanQuery) {}</pre>
- */
- public final Iterator<BooleanClause> iterator() { return clauses().iterator(); }
-
- /**
- * Expert: the Weight for BooleanQuery, used to
- * normalize, score and explain these queries.
- *
- * <p>NOTE: this API and implementation is subject to
- * change suddenly in the next release.</p>
- */
- protected class BooleanWeight extends Weight {
- /** The Similarity implementation. */
- protected Similarity similarity;
- protected ArrayList<Weight> weights;
- protected int maxCoord; // num optional + num required
- private final boolean disableCoord;
-
- public BooleanWeight(Searcher searcher, boolean disableCoord)
- throws IOException {
- this.similarity = getSimilarity(searcher);
- this.disableCoord = disableCoord;
- weights = new ArrayList<Weight>(clauses.size());
- for (int i = 0 ; i < clauses.size(); i++) {
- BooleanClause c = clauses.get(i);
- weights.add(c.getQuery().createWeight(searcher));
- if (!c.isProhibited()) maxCoord++;
- }
- }
-
- @Override
- public Query getQuery() { return BooleanQuery.this; }
-
- @Override
- public float getValue() { return getBoost(); }
-
- @Override
- public float sumOfSquaredWeights() throws IOException {
- float sum = 0.0f;
- for (int i = 0 ; i < weights.size(); i++) {
- // call sumOfSquaredWeights for all clauses in case of side effects
- float s = weights.get(i).sumOfSquaredWeights(); // sum sub weights
- if (!clauses.get(i).isProhibited())
- // only add to sum for non-prohibited clauses
- sum += s;
- }
-
- sum *= getBoost() * getBoost(); // boost each sub-weight
-
- return sum ;
- }
-
-
- @Override
- public void normalize(float norm) {
- norm *= getBoost(); // incorporate boost
- for (Weight w : weights) {
- // normalize all clauses, (even if prohibited in case of side affects)
- w.normalize(norm);
- }
- }
-
- @Override
- public Explanation explain(IndexReader reader, int doc)
- throws IOException {
- final int minShouldMatch =
- BooleanQuery.this.getMinimumNumberShouldMatch();
- ComplexExplanation sumExpl = new ComplexExplanation();
- sumExpl.setDescription("sum of:");
- int coord = 0;
- float sum = 0.0f;
- boolean fail = false;
- int shouldMatchCount = 0;
- Iterator<BooleanClause> cIter = clauses.iterator();
- for (Iterator<Weight> wIter = weights.iterator(); wIter.hasNext();) {
- Weight w = wIter.next();
- BooleanClause c = cIter.next();
- if (w.scorer(reader, true, true) == null) {
- if (c.isRequired()) {
- fail = true;
- Explanation r = new Explanation(0.0f, "no match on required clause (" + c.getQuery().toString() + ")");
- sumExpl.addDetail(r);
- }
- continue;
- }
- Explanation e = w.explain(reader, doc);
- 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() == 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) {
- sumExpl.setMatch(Boolean.FALSE);
- sumExpl.setValue(0.0f);
- sumExpl.setDescription
- ("Failure to meet condition(s) of required/prohibited clause(s)");
- return sumExpl;
- } else if (shouldMatchCount < minShouldMatch) {
- sumExpl.setMatch(Boolean.FALSE);
- sumExpl.setValue(0.0f);
- sumExpl.setDescription("Failure to match minimum number "+
- "of optional clauses: " + minShouldMatch);
- return sumExpl;
- }
-
- sumExpl.setMatch(0 < coord ? Boolean.TRUE : Boolean.FALSE);
- sumExpl.setValue(sum);
-
- final float coordFactor = disableCoord ? 1.0f : similarity.coord(coord, maxCoord);
- if (coordFactor == 1.0f) {
- 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;
- }
- }
-
- @Override
- public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer)
- throws IOException {
- List<Scorer> required = new ArrayList<Scorer>();
- List<Scorer> prohibited = new ArrayList<Scorer>();
- List<Scorer> optional = new ArrayList<Scorer>();
- Iterator<BooleanClause> cIter = clauses.iterator();
- for (Weight w : weights) {
- BooleanClause c = cIter.next();
- 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.size() == 0 && prohibited.size() < 32) {
- return new BooleanScorer(this, disableCoord, similarity, minNrShouldMatch, optional, prohibited, maxCoord);
- }
-
- if (required.size() == 0 && optional.size() == 0) {
- // no required and optional clauses.
- return null;
- } else if (optional.size() < 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(this, disableCoord, similarity, minNrShouldMatch, required, prohibited, optional, maxCoord);
- }
-
- @Override
- public boolean scoresDocsOutOfOrder() {
- int numProhibited = 0;
- for (BooleanClause c : 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;
- }
-
- }
-
- @Override
- public Weight createWeight(Searcher searcher) throws IOException {
- return new BooleanWeight(searcher, disableCoord);
- }
-
- @Override
- public Query rewrite(IndexReader reader) throws IOException {
- if (minNrShouldMatch == 0 && clauses.size() == 1) { // optimize 1-clause queries
- BooleanClause c = clauses.get(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.size(); i++) {
- BooleanClause c = clauses.get(i);
- Query query = c.getQuery().rewrite(reader);
- if (query != c.getQuery()) { // clause rewrote: must clone
- if (clone == null)
- clone = (BooleanQuery)this.clone();
- clone.clauses.set(i, new BooleanClause(query, c.getOccur()));
- }
- }
- if (clone != null) {
- return clone; // some clauses rewrote
- } else
- return this; // no clauses rewrote
- }
-
- // inherit javadoc
- @Override
- public void extractTerms(Set<Term> terms) {
- for (BooleanClause clause : clauses) {
- clause.getQuery().extractTerms(terms);
- }
- }
-
- @Override @SuppressWarnings("unchecked")
- public Object clone() {
- BooleanQuery clone = (BooleanQuery)super.clone();
- clone.clauses = (ArrayList<BooleanClause>) this.clauses.clone();
- return clone;
- }
-
- /** Prints a user-readable version of this query. */
- @Override
- public String toString(String field) {
- StringBuilder buffer = new StringBuilder();
- boolean needParens=(getBoost() != 1.0) || (getMinimumNumberShouldMatch()>0) ;
- if (needParens) {
- buffer.append("(");
- }
-
- for (int i = 0 ; i < clauses.size(); i++) {
- BooleanClause c = clauses.get(i);
- if (c.isProhibited())
- buffer.append("-");
- else if (c.isRequired())
- buffer.append("+");
-
- Query subQuery = c.getQuery();
- if (subQuery != null) {
- if (subQuery instanceof 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.size()-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();
- }
-
- /** Returns true iff <code>o</code> is equal to this. */
- @Override
- 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)
- && this.getMinimumNumberShouldMatch() == other.getMinimumNumberShouldMatch()
- && this.disableCoord == other.disableCoord;
- }
-
- /** Returns a hash code value for this object.*/
- @Override
- public int hashCode() {
- return Float.floatToIntBits(getBoost()) ^ clauses.hashCode()
- + getMinimumNumberShouldMatch() + (disableCoord ? 17:0);
- }
-
-}