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.util.List;
21
import java.io.IOException;
23
import org.apache.lucene.util.ScorerDocQueue;
25
/** A Scorer for OR like queries, counterpart of <code>ConjunctionScorer</code>.
26
* This Scorer implements {@link Scorer#skipTo(int)} and uses skipTo() on the given Scorers.
28
class DisjunctionSumScorer extends Scorer {
29
/** The number of subscorers. */
30
private final int nrScorers;
32
/** The subscorers. */
33
protected final List<Scorer> subScorers;
35
/** The minimum number of scorers that should match. */
36
private final int minimumNrMatchers;
38
/** The scorerDocQueue contains all subscorers ordered by their current doc(),
39
* with the minimum at the top.
40
* <br>The scorerDocQueue is initialized the first time next() or skipTo() is called.
41
* <br>An exhausted scorer is immediately removed from the scorerDocQueue.
42
* <br>If less than the minimumNrMatchers scorers
43
* remain in the scorerDocQueue next() and skipTo() return false.
45
* After each to call to next() or skipTo()
46
* <code>currentSumScore</code> is the total score of the current matching doc,
47
* <code>nrMatchers</code> is the number of matching scorers,
48
* and all scorers are after the matching doc, or are exhausted.
50
private ScorerDocQueue scorerDocQueue;
52
/** The document number of the current match. */
53
private int currentDoc = -1;
55
/** The number of subscorers that provide the current match. */
56
protected int nrMatchers = -1;
58
private double currentScore = Float.NaN;
60
/** Construct a <code>DisjunctionScorer</code>.
61
* @param weight The weight to be used.
62
* @param subScorers A collection of at least two subscorers.
63
* @param minimumNrMatchers The positive minimum number of subscorers that should
64
* match to match this query.
65
* <br>When <code>minimumNrMatchers</code> is bigger than
66
* the number of <code>subScorers</code>,
67
* no matches will be produced.
68
* <br>When minimumNrMatchers equals the number of subScorers,
69
* it more efficient to use <code>ConjunctionScorer</code>.
71
public DisjunctionSumScorer(Weight weight, List<Scorer> subScorers, int minimumNrMatchers) throws IOException {
74
nrScorers = subScorers.size();
76
if (minimumNrMatchers <= 0) {
77
throw new IllegalArgumentException("Minimum nr of matchers must be positive");
80
throw new IllegalArgumentException("There must be at least 2 subScorers");
83
this.minimumNrMatchers = minimumNrMatchers;
84
this.subScorers = subScorers;
89
/** Construct a <code>DisjunctionScorer</code>, using one as the minimum number
90
* of matching subscorers.
92
public DisjunctionSumScorer(Weight weight, List<Scorer> subScorers) throws IOException {
93
this(weight, subScorers, 1);
96
/** Called the first time next() or skipTo() is called to
97
* initialize <code>scorerDocQueue</code>.
99
private void initScorerDocQueue() throws IOException {
100
scorerDocQueue = new ScorerDocQueue(nrScorers);
101
for (Scorer se : subScorers) {
102
if (se.nextDoc() != NO_MORE_DOCS) {
103
scorerDocQueue.insert(se);
108
/** Scores and collects all matching documents.
109
* @param collector The collector to which all matching documents are passed through.
112
public void score(Collector collector) throws IOException {
113
collector.setScorer(this);
114
while (nextDoc() != NO_MORE_DOCS) {
115
collector.collect(currentDoc);
119
/** Expert: Collects matching documents in a range. Hook for optimization.
120
* Note that {@link #next()} must be called once before this method is called
121
* for the first time.
122
* @param collector The collector to which all matching documents are passed through.
123
* @param max Do not score documents past this.
124
* @return true if more matching documents may remain.
127
protected boolean score(Collector collector, int max, int firstDocID) throws IOException {
128
// firstDocID is ignored since nextDoc() sets 'currentDoc'
129
collector.setScorer(this);
130
while (currentDoc < max) {
131
collector.collect(currentDoc);
132
if (nextDoc() == NO_MORE_DOCS) {
140
public int nextDoc() throws IOException {
141
if (scorerDocQueue.size() < minimumNrMatchers || !advanceAfterCurrent()) {
142
currentDoc = NO_MORE_DOCS;
147
/** Advance all subscorers after the current document determined by the
148
* top of the <code>scorerDocQueue</code>.
149
* Repeat until at least the minimum number of subscorers match on the same
150
* document and all subscorers are after that document or are exhausted.
151
* <br>On entry the <code>scorerDocQueue</code> has at least <code>minimumNrMatchers</code>
152
* available. At least the scorer with the minimum document number will be advanced.
153
* @return true iff there is a match.
154
* <br>In case there is a match, </code>currentDoc</code>, </code>currentSumScore</code>,
155
* and </code>nrMatchers</code> describe the match.
157
* TODO: Investigate whether it is possible to use skipTo() when
158
* the minimum number of matchers is bigger than one, ie. try and use the
159
* character of ConjunctionScorer for the minimum number of matchers.
160
* Also delay calling score() on the sub scorers until the minimum number of
161
* matchers is reached.
162
* <br>For this, a Scorer array with minimumNrMatchers elements might
163
* hold Scorers at currentDoc that are temporarily popped from scorerQueue.
165
protected boolean advanceAfterCurrent() throws IOException {
166
do { // repeat until minimum nr of matchers
167
currentDoc = scorerDocQueue.topDoc();
168
currentScore = scorerDocQueue.topScore();
170
do { // Until all subscorers are after currentDoc
171
if (!scorerDocQueue.topNextAndAdjustElsePop()) {
172
if (scorerDocQueue.size() == 0) {
173
break; // nothing more to advance, check for last match.
176
if (scorerDocQueue.topDoc() != currentDoc) {
177
break; // All remaining subscorers are after currentDoc.
179
currentScore += scorerDocQueue.topScore();
183
if (nrMatchers >= minimumNrMatchers) {
185
} else if (scorerDocQueue.size() < minimumNrMatchers) {
191
/** Returns the score of the current document matching the query.
192
* Initially invalid, until {@link #nextDoc()} is called the first time.
195
public float score() throws IOException { return (float)currentScore; }
202
/** Returns the number of subscorers matching the current document.
203
* Initially invalid, until {@link #nextDoc()} is called the first time.
205
public int nrMatchers() {
210
* Advances to the first match beyond the current whose document number is
211
* greater than or equal to a given target. <br>
212
* The implementation uses the skipTo() method on the subscorers.
215
* The target document number.
216
* @return the document whose number is greater than or equal to the given
217
* target, or -1 if none exist.
220
public int advance(int target) throws IOException {
221
if (scorerDocQueue.size() < minimumNrMatchers) {
222
return currentDoc = NO_MORE_DOCS;
224
if (target <= currentDoc) {
228
if (scorerDocQueue.topDoc() >= target) {
229
return advanceAfterCurrent() ? currentDoc : (currentDoc = NO_MORE_DOCS);
230
} else if (!scorerDocQueue.topSkipToAndAdjustElsePop(target)) {
231
if (scorerDocQueue.size() < minimumNrMatchers) {
232
return currentDoc = NO_MORE_DOCS;