1 package org.apache.lucene.search;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.List;
24 import org.apache.lucene.search.BooleanClause.Occur;
26 /* See the description in BooleanScorer.java, comparing
27 * BooleanScorer & BooleanScorer2 */
29 /** An alternative to BooleanScorer that also allows a minimum number
30 * of optional scorers that should match.
31 * <br>Implements skipTo(), and has no limitations on the numbers of added scorers.
32 * <br>Uses ConjunctionScorer, DisjunctionScorer, ReqOptScorer and ReqExclScorer.
34 class BooleanScorer2 extends Scorer {
36 private final List<Scorer> requiredScorers;
37 private final List<Scorer> optionalScorers;
38 private final List<Scorer> prohibitedScorers;
40 private class Coordinator {
41 float[] coordFactors = null;
42 int maxCoord = 0; // to be increased for each non prohibited scorer
43 int nrMatchers; // to be increased by score() of match counting scorers.
45 void init(Similarity sim, boolean disableCoord) { // use after all scorers have been added.
46 coordFactors = new float[optionalScorers.size() + requiredScorers.size() + 1];
47 for (int i = 0; i < coordFactors.length; i++) {
48 coordFactors[i] = disableCoord ? 1.0f : sim.coord(i, maxCoord);
53 private final Coordinator coordinator;
55 /** The scorer to which all scoring will be delegated,
56 * except for computing and using the coordination factor.
58 private final Scorer countingSumScorer;
60 /** The number of optionalScorers that need to match (if there are any) */
61 private final int minNrShouldMatch;
66 * Creates a {@link Scorer} with the given similarity and lists of required,
67 * prohibited and optional scorers. In no required scorers are added, at least
68 * one of the optional scorers will have to match during the search.
71 * The BooleanWeight to be used.
73 * If this parameter is true, coordination level matching
74 * ({@link Similarity#coord(int, int)}) is not used.
75 * @param minNrShouldMatch
76 * The minimum number of optional added scorers that should match
77 * during the search. In case no required scorers are added, at least
78 * one of the optional scorers will have to match during the search.
80 * the list of required scorers.
82 * the list of prohibited scorers.
84 * the list of optional scorers.
86 public BooleanScorer2(Weight weight, boolean disableCoord, Similarity similarity, int minNrShouldMatch,
87 List<Scorer> required, List<Scorer> prohibited, List<Scorer> optional, int maxCoord) throws IOException {
89 if (minNrShouldMatch < 0) {
90 throw new IllegalArgumentException("Minimum number of optional scorers should not be negative");
92 coordinator = new Coordinator();
93 this.minNrShouldMatch = minNrShouldMatch;
94 coordinator.maxCoord = maxCoord;
96 optionalScorers = optional;
97 requiredScorers = required;
98 prohibitedScorers = prohibited;
100 coordinator.init(similarity, disableCoord);
101 countingSumScorer = makeCountingSumScorer(disableCoord, similarity);
104 /** Count a scorer as a single match. */
105 private class SingleMatchScorer extends Scorer {
106 private Scorer scorer;
107 private int lastScoredDoc = -1;
108 // Save the score of lastScoredDoc, so that we don't compute it more than
110 private float lastDocScore = Float.NaN;
112 SingleMatchScorer(Scorer scorer) {
113 super(scorer.weight);
114 this.scorer = scorer;
118 public float score() throws IOException {
120 if (doc >= lastScoredDoc) {
121 if (doc > lastScoredDoc) {
122 lastDocScore = scorer.score();
125 coordinator.nrMatchers++;
132 return scorer.docID();
136 public int nextDoc() throws IOException {
137 return scorer.nextDoc();
141 public int advance(int target) throws IOException {
142 return scorer.advance(target);
146 private Scorer countingDisjunctionSumScorer(final List<Scorer> scorers,
147 int minNrShouldMatch) throws IOException {
148 // each scorer from the list counted as a single matcher
149 return new DisjunctionSumScorer(weight, scorers, minNrShouldMatch) {
150 private int lastScoredDoc = -1;
151 // Save the score of lastScoredDoc, so that we don't compute it more than
153 private float lastDocScore = Float.NaN;
154 @Override public float score() throws IOException {
156 if (doc >= lastScoredDoc) {
157 if (doc > lastScoredDoc) {
158 lastDocScore = super.score();
161 coordinator.nrMatchers += super.nrMatchers;
168 private Scorer countingConjunctionSumScorer(boolean disableCoord,
169 Similarity similarity,
170 List<Scorer> requiredScorers) throws IOException {
171 // each scorer from the list counted as a single matcher
172 final int requiredNrMatchers = requiredScorers.size();
173 return new ConjunctionScorer(weight, disableCoord ? 1.0f : similarity.coord(requiredScorers.size(), requiredScorers.size()), requiredScorers) {
174 private int lastScoredDoc = -1;
175 // Save the score of lastScoredDoc, so that we don't compute it more than
177 private float lastDocScore = Float.NaN;
178 @Override public float score() throws IOException {
180 if (doc >= lastScoredDoc) {
181 if (doc > lastScoredDoc) {
182 lastDocScore = super.score();
185 coordinator.nrMatchers += requiredNrMatchers;
187 // All scorers match, so defaultSimilarity super.score() always has 1 as
188 // the coordination factor.
189 // Therefore the sum of the scores of the requiredScorers
196 private Scorer dualConjunctionSumScorer(boolean disableCoord,
197 Similarity similarity,
198 Scorer req1, Scorer req2) throws IOException { // non counting.
199 return new ConjunctionScorer(weight, disableCoord ? 1.0f : similarity.coord(2, 2), new Scorer[]{req1, req2});
200 // All scorers match, so defaultSimilarity always has 1 as
201 // the coordination factor.
202 // Therefore the sum of the scores of two scorers
206 /** Returns the scorer to be used for match counting and score summing.
207 * Uses requiredScorers, optionalScorers and prohibitedScorers.
209 private Scorer makeCountingSumScorer(boolean disableCoord,
210 Similarity similarity) throws IOException { // each scorer counted as a single matcher
211 return (requiredScorers.size() == 0)
212 ? makeCountingSumScorerNoReq(disableCoord, similarity)
213 : makeCountingSumScorerSomeReq(disableCoord, similarity);
216 private Scorer makeCountingSumScorerNoReq(boolean disableCoord, Similarity similarity) throws IOException { // No required scorers
217 // minNrShouldMatch optional scorers are required, but at least 1
218 int nrOptRequired = (minNrShouldMatch < 1) ? 1 : minNrShouldMatch;
219 Scorer requiredCountingSumScorer;
220 if (optionalScorers.size() > nrOptRequired)
221 requiredCountingSumScorer = countingDisjunctionSumScorer(optionalScorers, nrOptRequired);
222 else if (optionalScorers.size() == 1)
223 requiredCountingSumScorer = new SingleMatchScorer(optionalScorers.get(0));
225 requiredCountingSumScorer = countingConjunctionSumScorer(disableCoord, similarity, optionalScorers);
227 return addProhibitedScorers(requiredCountingSumScorer);
230 private Scorer makeCountingSumScorerSomeReq(boolean disableCoord, Similarity similarity) throws IOException { // At least one required scorer.
231 if (optionalScorers.size() == minNrShouldMatch) { // all optional scorers also required.
232 ArrayList<Scorer> allReq = new ArrayList<Scorer>(requiredScorers);
233 allReq.addAll(optionalScorers);
234 return addProhibitedScorers(countingConjunctionSumScorer(disableCoord, similarity, allReq));
235 } else { // optionalScorers.size() > minNrShouldMatch, and at least one required scorer
236 Scorer requiredCountingSumScorer =
237 requiredScorers.size() == 1
238 ? new SingleMatchScorer(requiredScorers.get(0))
239 : countingConjunctionSumScorer(disableCoord, similarity, requiredScorers);
240 if (minNrShouldMatch > 0) { // use a required disjunction scorer over the optional scorers
241 return addProhibitedScorers(
242 dualConjunctionSumScorer( // non counting
245 requiredCountingSumScorer,
246 countingDisjunctionSumScorer(
249 } else { // minNrShouldMatch == 0
250 return new ReqOptSumScorer(
251 addProhibitedScorers(requiredCountingSumScorer),
252 optionalScorers.size() == 1
253 ? new SingleMatchScorer(optionalScorers.get(0))
254 // require 1 in combined, optional scorer.
255 : countingDisjunctionSumScorer(optionalScorers, 1));
260 /** Returns the scorer to be used for match counting and score summing.
261 * Uses the given required scorer and the prohibitedScorers.
262 * @param requiredCountingSumScorer A required scorer already built.
264 private Scorer addProhibitedScorers(Scorer requiredCountingSumScorer) throws IOException
266 return (prohibitedScorers.size() == 0)
267 ? requiredCountingSumScorer // no prohibited
268 : new ReqExclScorer(requiredCountingSumScorer,
269 ((prohibitedScorers.size() == 1)
270 ? prohibitedScorers.get(0)
271 : new DisjunctionSumScorer(weight, prohibitedScorers)));
274 /** Scores and collects all matching documents.
275 * @param collector The collector to which all matching documents are passed through.
278 public void score(Collector collector) throws IOException {
279 collector.setScorer(this);
280 while ((doc = countingSumScorer.nextDoc()) != NO_MORE_DOCS) {
281 collector.collect(doc);
286 protected boolean score(Collector collector, int max, int firstDocID) throws IOException {
288 collector.setScorer(this);
290 collector.collect(doc);
291 doc = countingSumScorer.nextDoc();
293 return doc != NO_MORE_DOCS;
302 public int nextDoc() throws IOException {
303 return doc = countingSumScorer.nextDoc();
307 public float score() throws IOException {
308 coordinator.nrMatchers = 0;
309 float sum = countingSumScorer.score();
310 return sum * coordinator.coordFactors[coordinator.nrMatchers];
314 public float freq() {
315 return coordinator.nrMatchers;
319 public int advance(int target) throws IOException {
320 return doc = countingSumScorer.advance(target);
324 protected void visitSubScorers(Query parent, Occur relationship, ScorerVisitor<Query, Query, Scorer> visitor) {
325 super.visitSubScorers(parent, relationship, visitor);
326 final Query q = weight.getQuery();
327 for (Scorer s : optionalScorers) {
328 s.visitSubScorers(q, Occur.SHOULD, visitor);
330 for (Scorer s : prohibitedScorers) {
331 s.visitSubScorers(q, Occur.MUST_NOT, visitor);
333 for (Scorer s : requiredScorers) {
334 s.visitSubScorers(q, Occur.MUST, visitor);