| 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; |
| |
| import org.apache.lucene.search.BooleanClause.Occur; |
| import org.apache.lucene.search.BooleanQuery.BooleanWeight; |
| import org.apache.lucene.search.similarities.Similarity; |
| |
| /* 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 { |
| final float coordFactors[]; |
| |
| Coordinator(int maxCoord, boolean disableCoord) { |
| coordFactors = new float[optionalScorers.size() + requiredScorers.size() + 1]; |
| for (int i = 0; i < coordFactors.length; i++) { |
| coordFactors[i] = disableCoord ? 1.0f : ((BooleanWeight)weight).coord(i, maxCoord); |
| } |
| } |
| |
| int nrMatchers; // to be increased by score() of match counting scorers. |
| } |
| |
| 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(BooleanWeight weight, boolean disableCoord, 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"); |
| } |
| this.minNrShouldMatch = minNrShouldMatch; |
| |
| optionalScorers = optional; |
| requiredScorers = required; |
| prohibitedScorers = prohibited; |
| coordinator = new Coordinator(maxCoord, disableCoord); |
| |
| countingSumScorer = makeCountingSumScorer(disableCoord); |
| } |
| |
| /** 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 freq() throws IOException { |
| return 1; |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public long cost() { |
| return scorer.cost(); |
| } |
| } |
| |
| private Scorer countingDisjunctionSumScorer(final List<Scorer> scorers, |
| int minNrShouldMatch) throws IOException { |
| // each scorer from the list counted as a single matcher |
| if (minNrShouldMatch > 1) { |
| return new MinShouldMatchSumScorer(weight, scorers, minNrShouldMatch) { |
| @Override |
| public float score() throws IOException { |
| coordinator.nrMatchers += super.nrMatchers; |
| return super.score(); |
| } |
| }; |
| } else { |
| // we pass null for coord[] since we coordinate ourselves and override score() |
| return new DisjunctionSumScorer(weight, scorers.toArray(new Scorer[scorers.size()]), null) { |
| @Override |
| public float score() throws IOException { |
| coordinator.nrMatchers += super.nrMatchers; |
| return (float) super.score; |
| } |
| }; |
| } |
| } |
| |
| private Scorer countingConjunctionSumScorer(boolean disableCoord, |
| List<Scorer> requiredScorers) throws IOException { |
| // each scorer from the list counted as a single matcher |
| final int requiredNrMatchers = requiredScorers.size(); |
| return new ConjunctionScorer(weight, requiredScorers.toArray(new Scorer[requiredScorers.size()])) { |
| 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, |
| Scorer req1, Scorer req2) throws IOException { // non counting. |
| return new ConjunctionScorer(weight, 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) throws IOException { // each scorer counted as a single matcher |
| return (requiredScorers.size() == 0) |
| ? makeCountingSumScorerNoReq(disableCoord) |
| : makeCountingSumScorerSomeReq(disableCoord); |
| } |
| |
| private Scorer makeCountingSumScorerNoReq(boolean disableCoord) 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, optionalScorers); |
| } |
| return addProhibitedScorers(requiredCountingSumScorer); |
| } |
| |
| private Scorer makeCountingSumScorerSomeReq(boolean disableCoord) throws IOException { // At least one required scorer. |
| if (optionalScorers.size() == minNrShouldMatch) { // all optional scorers also required. |
| ArrayList<Scorer> allReq = new ArrayList<>(requiredScorers); |
| allReq.addAll(optionalScorers); |
| return addProhibitedScorers(countingConjunctionSumScorer(disableCoord, allReq)); |
| } else { // optionalScorers.size() > minNrShouldMatch, and at least one required scorer |
| Scorer requiredCountingSumScorer = |
| requiredScorers.size() == 1 |
| ? new SingleMatchScorer(requiredScorers.get(0)) |
| : countingConjunctionSumScorer(disableCoord, requiredScorers); |
| if (minNrShouldMatch > 0) { // use a required disjunction scorer over the optional scorers |
| return addProhibitedScorers( |
| dualConjunctionSumScorer( // non counting |
| disableCoord, |
| 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 MinShouldMatchSumScorer(weight, prohibitedScorers))); |
| } |
| |
| @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 int freq() throws IOException { |
| return countingSumScorer.freq(); |
| } |
| |
| @Override |
| public int advance(int target) throws IOException { |
| return doc = countingSumScorer.advance(target); |
| } |
| |
| @Override |
| public long cost() { |
| return countingSumScorer.cost(); |
| } |
| |
| @Override |
| public Collection<ChildScorer> getChildren() { |
| ArrayList<ChildScorer> children = new ArrayList<>(); |
| for (Scorer s : optionalScorers) { |
| children.add(new ChildScorer(s, "SHOULD")); |
| } |
| for (Scorer s : prohibitedScorers) { |
| children.add(new ChildScorer(s, "MUST_NOT")); |
| } |
| for (Scorer s : requiredScorers) { |
| children.add(new ChildScorer(s, "MUST")); |
| } |
| return children; |
| } |
| } |