blob: 1a37300d68753d92f8dfcfd9b2e9d7ef8b8a11cb [file] [log] [blame]
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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Base class for Scorers that score disjunctions.
*/
abstract class DisjunctionScorer extends Scorer {
private final boolean needsScores;
private final DisiPriorityQueue<Scorer> subScorers;
private final long cost;
/** Linked list of scorers which are on the current doc */
private DisiWrapper<Scorer> topScorers;
protected DisjunctionScorer(Weight weight, List<Scorer> subScorers, boolean needsScores) {
super(weight);
if (subScorers.size() <= 1) {
throw new IllegalArgumentException("There must be at least 2 subScorers");
}
this.subScorers = new DisiPriorityQueue<Scorer>(subScorers.size());
long cost = 0;
for (Scorer scorer : subScorers) {
final DisiWrapper<Scorer> w = new DisiWrapper<>(scorer);
cost += w.cost;
this.subScorers.add(w);
}
this.cost = cost;
this.needsScores = needsScores;
}
@Override
public TwoPhaseIterator asTwoPhaseIterator() {
float sumMatchCost = 0;
long sumApproxCost = 0;
// Compute matchCost as the avarage over the matchCost of the subScorers.
// This is weighted by the cost, which is an expected number of matching documents.
for (DisiWrapper<Scorer> w : subScorers) {
if (w.twoPhaseView != null) {
long costWeight = (w.cost <= 1) ? 1 : w.cost;
sumMatchCost += w.twoPhaseView.matchCost() * costWeight;
sumApproxCost += costWeight;
}
}
if (sumApproxCost == 0) { // no sub scorer supports approximations
return null;
}
final float matchCost = sumMatchCost / sumApproxCost;
// note it is important to share the same pq as this scorer so that
// rebalancing the pq through the approximation will also rebalance
// the pq in this scorer.
return new TwoPhaseIterator(new DisjunctionDISIApproximation<Scorer>(subScorers)) {
@Override
public boolean matches() throws IOException {
DisiWrapper<Scorer> topScorers = subScorers.topList();
// remove the head of the list as long as it does not match
while (topScorers.twoPhaseView != null && ! topScorers.twoPhaseView.matches()) {
topScorers = topScorers.next;
if (topScorers == null) {
return false;
}
}
// now we know we have at least one match since the first element of 'matchList' matches
if (needsScores) {
// if scores or freqs are needed, we also need to remove scorers
// from the top list that do not actually match
DisiWrapper<Scorer> previous = topScorers;
for (DisiWrapper<Scorer> w = topScorers.next; w != null; w = w.next) {
if (w.twoPhaseView != null && ! w.twoPhaseView.matches()) {
// w does not match, remove it
previous.next = w.next;
} else {
previous = w;
}
}
} else {
// since we don't need scores, let's pretend we have a single match
topScorers.next = null;
}
// We need to explicitely set the list of top scorers to avoid the
// laziness of DisjunctionScorer.score() that would take all scorers
// positioned on the same doc as the top of the pq, including
// non-matching scorers
DisjunctionScorer.this.topScorers = topScorers;
return true;
}
@Override
public float matchCost() {
return matchCost;
}
};
}
@Override
public final long cost() {
return cost;
}
@Override
public final int docID() {
return subScorers.top().doc;
}
@Override
public final int nextDoc() throws IOException {
topScorers = null;
DisiWrapper<Scorer> top = subScorers.top();
final int doc = top.doc;
do {
top.doc = top.iterator.nextDoc();
top = subScorers.updateTop();
} while (top.doc == doc);
return top.doc;
}
@Override
public final int advance(int target) throws IOException {
topScorers = null;
DisiWrapper<Scorer> top = subScorers.top();
do {
top.doc = top.iterator.advance(target);
top = subScorers.updateTop();
} while (top.doc < target);
return top.doc;
}
@Override
public final int freq() throws IOException {
if (topScorers == null) {
topScorers = subScorers.topList();
}
int freq = 1;
for (DisiWrapper<Scorer> w = topScorers.next; w != null; w = w.next) {
freq += 1;
}
return freq;
}
@Override
public final float score() throws IOException {
if (topScorers == null) {
topScorers = subScorers.topList();
}
return score(topScorers);
}
/** Compute the score for the given linked list of scorers. */
protected abstract float score(DisiWrapper<Scorer> topList) throws IOException;
@Override
public final Collection<ChildScorer> getChildren() {
ArrayList<ChildScorer> children = new ArrayList<>();
for (DisiWrapper<Scorer> scorer : subScorers) {
children.add(new ChildScorer(scorer.iterator, "SHOULD"));
}
return children;
}
}