pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / BooleanScorer2.java
1 package org.apache.lucene.search;
2
3 /**
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import org.apache.lucene.search.BooleanClause.Occur;
25
26 /* See the description in BooleanScorer.java, comparing
27  * BooleanScorer & BooleanScorer2 */
28
29 /** An alternative to BooleanScorer that also allows a minimum number
30  * of optional scorers that should match.
31  * <br>Implements skipTo(), and has no limitations on the numbers of added scorers.
32  * <br>Uses ConjunctionScorer, DisjunctionScorer, ReqOptScorer and ReqExclScorer.
33  */
34 class BooleanScorer2 extends Scorer {
35   
36   private final List<Scorer> requiredScorers;
37   private final List<Scorer> optionalScorers;
38   private final List<Scorer> prohibitedScorers;
39
40   private class Coordinator {
41     float[] coordFactors = null;
42     int maxCoord = 0; // to be increased for each non prohibited scorer
43     int nrMatchers; // to be increased by score() of match counting scorers.
44     
45     void init(Similarity sim, boolean disableCoord) { // use after all scorers have been added.
46       coordFactors = new float[optionalScorers.size() + requiredScorers.size() + 1];
47       for (int i = 0; i < coordFactors.length; i++) {
48         coordFactors[i] = disableCoord ? 1.0f : sim.coord(i, maxCoord);
49       }
50     }
51   }
52
53   private final Coordinator coordinator;
54
55   /** The scorer to which all scoring will be delegated,
56    * except for computing and using the coordination factor.
57    */
58   private final Scorer countingSumScorer;
59
60   /** The number of optionalScorers that need to match (if there are any) */
61   private final int minNrShouldMatch;
62
63   private int doc = -1;
64
65   /**
66    * Creates a {@link Scorer} with the given similarity and lists of required,
67    * prohibited and optional scorers. In no required scorers are added, at least
68    * one of the optional scorers will have to match during the search.
69    * 
70    * @param weight
71    *          The BooleanWeight to be used.
72    * @param disableCoord
73    *          If this parameter is true, coordination level matching 
74    *          ({@link Similarity#coord(int, int)}) is not used.
75    * @param minNrShouldMatch
76    *          The minimum number of optional added scorers that should match
77    *          during the search. In case no required scorers are added, at least
78    *          one of the optional scorers will have to match during the search.
79    * @param required
80    *          the list of required scorers.
81    * @param prohibited
82    *          the list of prohibited scorers.
83    * @param optional
84    *          the list of optional scorers.
85    */
86   public BooleanScorer2(Weight weight, boolean disableCoord, Similarity similarity, int minNrShouldMatch,
87       List<Scorer> required, List<Scorer> prohibited, List<Scorer> optional, int maxCoord) throws IOException {
88     super(weight);
89     if (minNrShouldMatch < 0) {
90       throw new IllegalArgumentException("Minimum number of optional scorers should not be negative");
91     }
92     coordinator = new Coordinator();
93     this.minNrShouldMatch = minNrShouldMatch;
94     coordinator.maxCoord = maxCoord;
95
96     optionalScorers = optional;
97     requiredScorers = required;    
98     prohibitedScorers = prohibited;
99     
100     coordinator.init(similarity, disableCoord);
101     countingSumScorer = makeCountingSumScorer(disableCoord, similarity);
102   }
103   
104   /** Count a scorer as a single match. */
105   private class SingleMatchScorer extends Scorer {
106     private Scorer scorer;
107     private int lastScoredDoc = -1;
108     // Save the score of lastScoredDoc, so that we don't compute it more than
109     // once in score().
110     private float lastDocScore = Float.NaN;
111
112     SingleMatchScorer(Scorer scorer) {
113       super(scorer.weight);
114       this.scorer = scorer;
115     }
116
117     @Override
118     public float score() throws IOException {
119       int doc = docID();
120       if (doc >= lastScoredDoc) {
121         if (doc > lastScoredDoc) {
122           lastDocScore = scorer.score();
123           lastScoredDoc = doc;
124         }
125         coordinator.nrMatchers++;
126       }
127       return lastDocScore;
128     }
129
130     @Override
131     public int docID() {
132       return scorer.docID();
133     }
134
135     @Override
136     public int nextDoc() throws IOException {
137       return scorer.nextDoc();
138     }
139
140     @Override
141     public int advance(int target) throws IOException {
142       return scorer.advance(target);
143     }
144   }
145
146   private Scorer countingDisjunctionSumScorer(final List<Scorer> scorers,
147       int minNrShouldMatch) throws IOException {
148     // each scorer from the list counted as a single matcher
149     return new DisjunctionSumScorer(weight, scorers, minNrShouldMatch) {
150       private int lastScoredDoc = -1;
151       // Save the score of lastScoredDoc, so that we don't compute it more than
152       // once in score().
153       private float lastDocScore = Float.NaN;
154       @Override public float score() throws IOException {
155         int doc = docID();
156         if (doc >= lastScoredDoc) {
157           if (doc > lastScoredDoc) {
158             lastDocScore = super.score();
159             lastScoredDoc = doc;
160           }
161           coordinator.nrMatchers += super.nrMatchers;
162         }
163         return lastDocScore;
164       }
165     };
166   }
167
168   private Scorer countingConjunctionSumScorer(boolean disableCoord,
169                                               Similarity similarity,
170                                               List<Scorer> requiredScorers) throws IOException {
171     // each scorer from the list counted as a single matcher
172     final int requiredNrMatchers = requiredScorers.size();
173     return new ConjunctionScorer(weight, disableCoord ? 1.0f : similarity.coord(requiredScorers.size(), requiredScorers.size()), requiredScorers) {
174       private int lastScoredDoc = -1;
175       // Save the score of lastScoredDoc, so that we don't compute it more than
176       // once in score().
177       private float lastDocScore = Float.NaN;
178       @Override public float score() throws IOException {
179         int doc = docID();
180         if (doc >= lastScoredDoc) {
181           if (doc > lastScoredDoc) {
182             lastDocScore = super.score();
183             lastScoredDoc = doc;
184           }
185           coordinator.nrMatchers += requiredNrMatchers;
186         }
187         // All scorers match, so defaultSimilarity super.score() always has 1 as
188         // the coordination factor.
189         // Therefore the sum of the scores of the requiredScorers
190         // is used as score.
191         return lastDocScore;
192       }
193     };
194   }
195
196   private Scorer dualConjunctionSumScorer(boolean disableCoord,
197                                           Similarity similarity,
198                                           Scorer req1, Scorer req2) throws IOException { // non counting.
199     return new ConjunctionScorer(weight, disableCoord ? 1.0f : similarity.coord(2, 2), new Scorer[]{req1, req2});
200     // All scorers match, so defaultSimilarity always has 1 as
201     // the coordination factor.
202     // Therefore the sum of the scores of two scorers
203     // is used as score.
204   }
205
206   /** Returns the scorer to be used for match counting and score summing.
207    * Uses requiredScorers, optionalScorers and prohibitedScorers.
208    */
209   private Scorer makeCountingSumScorer(boolean disableCoord,
210                                        Similarity similarity) throws IOException { // each scorer counted as a single matcher
211     return (requiredScorers.size() == 0)
212       ? makeCountingSumScorerNoReq(disableCoord, similarity)
213       : makeCountingSumScorerSomeReq(disableCoord, similarity);
214   }
215
216   private Scorer makeCountingSumScorerNoReq(boolean disableCoord, Similarity similarity) throws IOException { // No required scorers
217     // minNrShouldMatch optional scorers are required, but at least 1
218     int nrOptRequired = (minNrShouldMatch < 1) ? 1 : minNrShouldMatch;
219     Scorer requiredCountingSumScorer;
220     if (optionalScorers.size() > nrOptRequired)
221       requiredCountingSumScorer = countingDisjunctionSumScorer(optionalScorers, nrOptRequired);
222     else if (optionalScorers.size() == 1)
223       requiredCountingSumScorer = new SingleMatchScorer(optionalScorers.get(0));
224     else {
225       requiredCountingSumScorer = countingConjunctionSumScorer(disableCoord, similarity, optionalScorers);
226     }
227     return addProhibitedScorers(requiredCountingSumScorer);
228   }
229
230   private Scorer makeCountingSumScorerSomeReq(boolean disableCoord, Similarity similarity) throws IOException { // At least one required scorer.
231     if (optionalScorers.size() == minNrShouldMatch) { // all optional scorers also required.
232       ArrayList<Scorer> allReq = new ArrayList<Scorer>(requiredScorers);
233       allReq.addAll(optionalScorers);
234       return addProhibitedScorers(countingConjunctionSumScorer(disableCoord, similarity, allReq));
235     } else { // optionalScorers.size() > minNrShouldMatch, and at least one required scorer
236       Scorer requiredCountingSumScorer =
237             requiredScorers.size() == 1
238             ? new SingleMatchScorer(requiredScorers.get(0))
239             : countingConjunctionSumScorer(disableCoord, similarity, requiredScorers);
240       if (minNrShouldMatch > 0) { // use a required disjunction scorer over the optional scorers
241         return addProhibitedScorers( 
242                       dualConjunctionSumScorer( // non counting
243                               disableCoord,
244                               similarity,
245                               requiredCountingSumScorer,
246                               countingDisjunctionSumScorer(
247                                       optionalScorers,
248                                       minNrShouldMatch)));
249       } else { // minNrShouldMatch == 0
250         return new ReqOptSumScorer(
251                       addProhibitedScorers(requiredCountingSumScorer),
252                       optionalScorers.size() == 1
253                         ? new SingleMatchScorer(optionalScorers.get(0))
254                         // require 1 in combined, optional scorer.
255                         : countingDisjunctionSumScorer(optionalScorers, 1));
256       }
257     }
258   }
259   
260   /** Returns the scorer to be used for match counting and score summing.
261    * Uses the given required scorer and the prohibitedScorers.
262    * @param requiredCountingSumScorer A required scorer already built.
263    */
264   private Scorer addProhibitedScorers(Scorer requiredCountingSumScorer) throws IOException
265   {
266     return (prohibitedScorers.size() == 0)
267           ? requiredCountingSumScorer // no prohibited
268           : new ReqExclScorer(requiredCountingSumScorer,
269                               ((prohibitedScorers.size() == 1)
270                                 ? prohibitedScorers.get(0)
271                                 : new DisjunctionSumScorer(weight, prohibitedScorers)));
272   }
273
274   /** Scores and collects all matching documents.
275    * @param collector The collector to which all matching documents are passed through.
276    */
277   @Override
278   public void score(Collector collector) throws IOException {
279     collector.setScorer(this);
280     while ((doc = countingSumScorer.nextDoc()) != NO_MORE_DOCS) {
281       collector.collect(doc);
282     }
283   }
284   
285   @Override
286   protected boolean score(Collector collector, int max, int firstDocID) throws IOException {
287     doc = firstDocID;
288     collector.setScorer(this);
289     while (doc < max) {
290       collector.collect(doc);
291       doc = countingSumScorer.nextDoc();
292     }
293     return doc != NO_MORE_DOCS;
294   }
295
296   @Override
297   public int docID() {
298     return doc;
299   }
300   
301   @Override
302   public int nextDoc() throws IOException {
303     return doc = countingSumScorer.nextDoc();
304   }
305   
306   @Override
307   public float score() throws IOException {
308     coordinator.nrMatchers = 0;
309     float sum = countingSumScorer.score();
310     return sum * coordinator.coordFactors[coordinator.nrMatchers];
311   }
312
313   @Override
314   public float freq() {
315     return coordinator.nrMatchers;
316   }
317
318   @Override
319   public int advance(int target) throws IOException {
320     return doc = countingSumScorer.advance(target);
321   }
322
323   @Override
324   protected void visitSubScorers(Query parent, Occur relationship, ScorerVisitor<Query, Query, Scorer> visitor) {
325     super.visitSubScorers(parent, relationship, visitor);
326     final Query q = weight.getQuery();
327     for (Scorer s : optionalScorers) {
328       s.visitSubScorers(q, Occur.SHOULD, visitor);
329     }
330     for (Scorer s : prohibitedScorers) {
331       s.visitSubScorers(q, Occur.MUST_NOT, visitor);
332     }
333     for (Scorer s : requiredScorers) {
334       s.visitSubScorers(q, Occur.MUST, visitor);
335     }
336   }
337 }