--- /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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.search.BooleanClause.Occur;
+
+/* See the description in BooleanScorer.java, comparing
+ * BooleanScorer & BooleanScorer2 */
+
+/** An alternative to BooleanScorer that also allows a minimum number
+ * of optional scorers that should match.
+ * <br>Implements skipTo(), and has no limitations on the numbers of added scorers.
+ * <br>Uses ConjunctionScorer, DisjunctionScorer, ReqOptScorer and ReqExclScorer.
+ */
+class BooleanScorer2 extends Scorer {
+
+ private final List<Scorer> requiredScorers;
+ private final List<Scorer> optionalScorers;
+ private final List<Scorer> prohibitedScorers;
+
+ private class Coordinator {
+ float[] coordFactors = null;
+ int maxCoord = 0; // to be increased for each non prohibited scorer
+ int nrMatchers; // to be increased by score() of match counting scorers.
+
+ void init(Similarity sim, boolean disableCoord) { // use after all scorers have been added.
+ coordFactors = new float[optionalScorers.size() + requiredScorers.size() + 1];
+ for (int i = 0; i < coordFactors.length; i++) {
+ coordFactors[i] = disableCoord ? 1.0f : sim.coord(i, maxCoord);
+ }
+ }
+ }
+
+ private final Coordinator coordinator;
+
+ /** The scorer to which all scoring will be delegated,
+ * except for computing and using the coordination factor.
+ */
+ private final Scorer countingSumScorer;
+
+ /** The number of optionalScorers that need to match (if there are any) */
+ private final int minNrShouldMatch;
+
+ private int doc = -1;
+
+ /**
+ * Creates a {@link Scorer} with the given similarity and lists of required,
+ * prohibited and optional scorers. In no required scorers are added, at least
+ * one of the optional scorers will have to match during the search.
+ *
+ * @param weight
+ * The BooleanWeight to be used.
+ * @param disableCoord
+ * If this parameter is true, coordination level matching
+ * ({@link Similarity#coord(int, int)}) is not used.
+ * @param minNrShouldMatch
+ * The minimum number of optional added scorers that should match
+ * during the search. In case no required scorers are added, at least
+ * one of the optional scorers will have to match during the search.
+ * @param required
+ * the list of required scorers.
+ * @param prohibited
+ * the list of prohibited scorers.
+ * @param optional
+ * the list of optional scorers.
+ */
+ public BooleanScorer2(Weight weight, boolean disableCoord, Similarity similarity, int minNrShouldMatch,
+ List<Scorer> required, List<Scorer> prohibited, List<Scorer> optional, int maxCoord) throws IOException {
+ super(weight);
+ if (minNrShouldMatch < 0) {
+ throw new IllegalArgumentException("Minimum number of optional scorers should not be negative");
+ }
+ coordinator = new Coordinator();
+ this.minNrShouldMatch = minNrShouldMatch;
+ coordinator.maxCoord = maxCoord;
+
+ optionalScorers = optional;
+ requiredScorers = required;
+ prohibitedScorers = prohibited;
+
+ coordinator.init(similarity, disableCoord);
+ countingSumScorer = makeCountingSumScorer(disableCoord, similarity);
+ }
+
+ /** Count a scorer as a single match. */
+ private class SingleMatchScorer extends Scorer {
+ private Scorer scorer;
+ private int lastScoredDoc = -1;
+ // Save the score of lastScoredDoc, so that we don't compute it more than
+ // once in score().
+ private float lastDocScore = Float.NaN;
+
+ SingleMatchScorer(Scorer scorer) {
+ super(scorer.weight);
+ this.scorer = scorer;
+ }
+
+ @Override
+ public float score() throws IOException {
+ int doc = docID();
+ if (doc >= lastScoredDoc) {
+ if (doc > lastScoredDoc) {
+ lastDocScore = scorer.score();
+ lastScoredDoc = doc;
+ }
+ coordinator.nrMatchers++;
+ }
+ return lastDocScore;
+ }
+
+ @Override
+ public int docID() {
+ return scorer.docID();
+ }
+
+ @Override
+ public int nextDoc() throws IOException {
+ return scorer.nextDoc();
+ }
+
+ @Override
+ public int advance(int target) throws IOException {
+ return scorer.advance(target);
+ }
+ }
+
+ private Scorer countingDisjunctionSumScorer(final List<Scorer> scorers,
+ int minNrShouldMatch) throws IOException {
+ // each scorer from the list counted as a single matcher
+ return new DisjunctionSumScorer(weight, scorers, minNrShouldMatch) {
+ private int lastScoredDoc = -1;
+ // Save the score of lastScoredDoc, so that we don't compute it more than
+ // once in score().
+ private float lastDocScore = Float.NaN;
+ @Override public float score() throws IOException {
+ int doc = docID();
+ if (doc >= lastScoredDoc) {
+ if (doc > lastScoredDoc) {
+ lastDocScore = super.score();
+ lastScoredDoc = doc;
+ }
+ coordinator.nrMatchers += super.nrMatchers;
+ }
+ return lastDocScore;
+ }
+ };
+ }
+
+ private Scorer countingConjunctionSumScorer(boolean disableCoord,
+ Similarity similarity,
+ List<Scorer> requiredScorers) throws IOException {
+ // each scorer from the list counted as a single matcher
+ final int requiredNrMatchers = requiredScorers.size();
+ return new ConjunctionScorer(weight, disableCoord ? 1.0f : similarity.coord(requiredScorers.size(), requiredScorers.size()), requiredScorers) {
+ private int lastScoredDoc = -1;
+ // Save the score of lastScoredDoc, so that we don't compute it more than
+ // once in score().
+ private float lastDocScore = Float.NaN;
+ @Override public float score() throws IOException {
+ int doc = docID();
+ if (doc >= lastScoredDoc) {
+ if (doc > lastScoredDoc) {
+ lastDocScore = super.score();
+ lastScoredDoc = doc;
+ }
+ coordinator.nrMatchers += requiredNrMatchers;
+ }
+ // All scorers match, so defaultSimilarity super.score() always has 1 as
+ // the coordination factor.
+ // Therefore the sum of the scores of the requiredScorers
+ // is used as score.
+ return lastDocScore;
+ }
+ };
+ }
+
+ private Scorer dualConjunctionSumScorer(boolean disableCoord,
+ Similarity similarity,
+ Scorer req1, Scorer req2) throws IOException { // non counting.
+ return new ConjunctionScorer(weight, disableCoord ? 1.0f : similarity.coord(2, 2), new Scorer[]{req1, req2});
+ // All scorers match, so defaultSimilarity always has 1 as
+ // the coordination factor.
+ // Therefore the sum of the scores of two scorers
+ // is used as score.
+ }
+
+ /** Returns the scorer to be used for match counting and score summing.
+ * Uses requiredScorers, optionalScorers and prohibitedScorers.
+ */
+ private Scorer makeCountingSumScorer(boolean disableCoord,
+ Similarity similarity) throws IOException { // each scorer counted as a single matcher
+ return (requiredScorers.size() == 0)
+ ? makeCountingSumScorerNoReq(disableCoord, similarity)
+ : makeCountingSumScorerSomeReq(disableCoord, similarity);
+ }
+
+ private Scorer makeCountingSumScorerNoReq(boolean disableCoord, Similarity similarity) throws IOException { // No required scorers
+ // minNrShouldMatch optional scorers are required, but at least 1
+ int nrOptRequired = (minNrShouldMatch < 1) ? 1 : minNrShouldMatch;
+ Scorer requiredCountingSumScorer;
+ if (optionalScorers.size() > nrOptRequired)
+ requiredCountingSumScorer = countingDisjunctionSumScorer(optionalScorers, nrOptRequired);
+ else if (optionalScorers.size() == 1)
+ requiredCountingSumScorer = new SingleMatchScorer(optionalScorers.get(0));
+ else {
+ requiredCountingSumScorer = countingConjunctionSumScorer(disableCoord, similarity, optionalScorers);
+ }
+ return addProhibitedScorers(requiredCountingSumScorer);
+ }
+
+ private Scorer makeCountingSumScorerSomeReq(boolean disableCoord, Similarity similarity) throws IOException { // At least one required scorer.
+ if (optionalScorers.size() == minNrShouldMatch) { // all optional scorers also required.
+ ArrayList<Scorer> allReq = new ArrayList<Scorer>(requiredScorers);
+ allReq.addAll(optionalScorers);
+ return addProhibitedScorers(countingConjunctionSumScorer(disableCoord, similarity, allReq));
+ } else { // optionalScorers.size() > minNrShouldMatch, and at least one required scorer
+ Scorer requiredCountingSumScorer =
+ requiredScorers.size() == 1
+ ? new SingleMatchScorer(requiredScorers.get(0))
+ : countingConjunctionSumScorer(disableCoord, similarity, requiredScorers);
+ if (minNrShouldMatch > 0) { // use a required disjunction scorer over the optional scorers
+ return addProhibitedScorers(
+ dualConjunctionSumScorer( // non counting
+ disableCoord,
+ similarity,
+ requiredCountingSumScorer,
+ countingDisjunctionSumScorer(
+ optionalScorers,
+ minNrShouldMatch)));
+ } else { // minNrShouldMatch == 0
+ return new ReqOptSumScorer(
+ addProhibitedScorers(requiredCountingSumScorer),
+ optionalScorers.size() == 1
+ ? new SingleMatchScorer(optionalScorers.get(0))
+ // require 1 in combined, optional scorer.
+ : countingDisjunctionSumScorer(optionalScorers, 1));
+ }
+ }
+ }
+
+ /** Returns the scorer to be used for match counting and score summing.
+ * Uses the given required scorer and the prohibitedScorers.
+ * @param requiredCountingSumScorer A required scorer already built.
+ */
+ private Scorer addProhibitedScorers(Scorer requiredCountingSumScorer) throws IOException
+ {
+ return (prohibitedScorers.size() == 0)
+ ? requiredCountingSumScorer // no prohibited
+ : new ReqExclScorer(requiredCountingSumScorer,
+ ((prohibitedScorers.size() == 1)
+ ? prohibitedScorers.get(0)
+ : new DisjunctionSumScorer(weight, prohibitedScorers)));
+ }
+
+ /** Scores and collects all matching documents.
+ * @param collector The collector to which all matching documents are passed through.
+ */
+ @Override
+ public void score(Collector collector) throws IOException {
+ collector.setScorer(this);
+ while ((doc = countingSumScorer.nextDoc()) != NO_MORE_DOCS) {
+ collector.collect(doc);
+ }
+ }
+
+ @Override
+ protected boolean score(Collector collector, int max, int firstDocID) throws IOException {
+ doc = firstDocID;
+ collector.setScorer(this);
+ while (doc < max) {
+ collector.collect(doc);
+ doc = countingSumScorer.nextDoc();
+ }
+ return doc != NO_MORE_DOCS;
+ }
+
+ @Override
+ public int docID() {
+ return doc;
+ }
+
+ @Override
+ public int nextDoc() throws IOException {
+ return doc = countingSumScorer.nextDoc();
+ }
+
+ @Override
+ public float score() throws IOException {
+ coordinator.nrMatchers = 0;
+ float sum = countingSumScorer.score();
+ return sum * coordinator.coordFactors[coordinator.nrMatchers];
+ }
+
+ @Override
+ public float freq() {
+ return coordinator.nrMatchers;
+ }
+
+ @Override
+ public int advance(int target) throws IOException {
+ return doc = countingSumScorer.advance(target);
+ }
+
+ @Override
+ protected void visitSubScorers(Query parent, Occur relationship, ScorerVisitor<Query, Query, Scorer> visitor) {
+ super.visitSubScorers(parent, relationship, visitor);
+ final Query q = weight.getQuery();
+ for (Scorer s : optionalScorers) {
+ s.visitSubScorers(q, Occur.SHOULD, visitor);
+ }
+ for (Scorer s : prohibitedScorers) {
+ s.visitSubScorers(q, Occur.MUST_NOT, visitor);
+ }
+ for (Scorer s : requiredScorers) {
+ s.visitSubScorers(q, Occur.MUST, visitor);
+ }
+ }
+}