pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / BooleanScorer2.java
diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/BooleanScorer2.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/BooleanScorer2.java
new file mode 100644 (file)
index 0000000..3030c74
--- /dev/null
@@ -0,0 +1,337 @@
+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);
+    }
+  }
+}