pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / FilteredQuery.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 org.apache.lucene.index.IndexReader;
21 import org.apache.lucene.index.Term;
22 import org.apache.lucene.util.ToStringUtils;
23
24 import java.io.IOException;
25 import java.util.Set;
26
27
28 /**
29  * A query that applies a filter to the results of another query.
30  *
31  * <p>Note: the bits are retrieved from the filter each time this
32  * query is used in a search - use a CachingWrapperFilter to avoid
33  * regenerating the bits every time.
34  *
35  * <p>Created: Apr 20, 2004 8:58:29 AM
36  *
37  * @since   1.4
38  * @see     CachingWrapperFilter
39  */
40 public class FilteredQuery
41 extends Query {
42
43   Query query;
44   Filter filter;
45
46   /**
47    * Constructs a new query which applies a filter to the results of the original query.
48    * Filter.getDocIdSet() will be called every time this query is used in a search.
49    * @param query  Query to be filtered, cannot be <code>null</code>.
50    * @param filter Filter to apply to query results, cannot be <code>null</code>.
51    */
52   public FilteredQuery (Query query, Filter filter) {
53     this.query = query;
54     this.filter = filter;
55   }
56
57   /**
58    * Returns a Weight that applies the filter to the enclosed query's Weight.
59    * This is accomplished by overriding the Scorer returned by the Weight.
60    */
61   @Override
62   public Weight createWeight(final Searcher searcher) throws IOException {
63     final Weight weight = query.createWeight (searcher);
64     final Similarity similarity = query.getSimilarity(searcher);
65     return new Weight() {
66       private float value;
67         
68       // pass these methods through to enclosed query's weight
69       @Override
70       public float getValue() { return value; }
71       
72       @Override
73       public boolean scoresDocsOutOfOrder() {
74         return false;
75       }
76
77       public float sumOfSquaredWeights() throws IOException { 
78         return weight.sumOfSquaredWeights() * getBoost() * getBoost(); // boost sub-weight
79       }
80
81       @Override
82       public void normalize (float v) { 
83         weight.normalize(v * getBoost()); // incorporate boost
84         value = weight.getValue();
85       }
86
87       @Override
88       public Explanation explain (IndexReader ir, int i) throws IOException {
89         Explanation inner = weight.explain (ir, i);
90         Filter f = FilteredQuery.this.filter;
91         DocIdSet docIdSet = f.getDocIdSet(ir);
92         DocIdSetIterator docIdSetIterator = docIdSet == null ? DocIdSet.EMPTY_DOCIDSET.iterator() : docIdSet.iterator();
93         if (docIdSetIterator == null) {
94           docIdSetIterator = DocIdSet.EMPTY_DOCIDSET.iterator();
95         }
96         if (docIdSetIterator.advance(i) == i) {
97           return inner;
98         } else {
99           Explanation result = new Explanation
100             (0.0f, "failure to match filter: " + f.toString());
101           result.addDetail(inner);
102           return result;
103         }
104       }
105
106       // return this query
107       @Override
108       public Query getQuery() { return FilteredQuery.this; }
109
110       // return a filtering scorer
111       @Override
112       public Scorer scorer(IndexReader indexReader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
113         // Hackidy-Häck-Hack for backwards compatibility, as we cannot change IndexSearcher API in 3.x, but still want
114         // to move the searchWithFilter implementation to this class: to enable access to our scorer() implementation
115         // from IndexSearcher, we moved this method up to the main class. In Lucene trunk,
116         // FilteredQuery#getFilteredScorer is inlined here - in 3.x we delegate:
117         return FilteredQuery.getFilteredScorer(indexReader, similarity, weight, this, filter);
118       }
119     };
120   }
121   
122   /** Hackidy-Häck-Hack for backwards compatibility, as we cannot change IndexSearcher API in 3.x, but still want
123    * to move the searchWithFilter implementation to this class: to enable access to our scorer() implementation
124    * from IndexSearcher without instantiating a separate {@link Weight}, we make the inner implementation accessible.
125    * @param indexReader the atomic reader
126    * @param similarity the Similarity to use (deprecated)
127    * @param weight the weight to wrap
128    * @param wrapperWeight must be identical to {@code weight} for usage in {@link IndexSearcher}, but it is different inside this query
129    * @param filter the Filter to wrap
130    * @lucene.internal
131    */
132   static Scorer getFilteredScorer(final IndexReader indexReader, final Similarity similarity,
133                                   final Weight weight, final Weight wrapperWeight, final Filter filter) throws IOException {
134     assert filter != null;
135
136     final DocIdSet filterDocIdSet = filter.getDocIdSet(indexReader);
137     if (filterDocIdSet == null) {
138       // this means the filter does not accept any documents.
139       return null;
140     }
141     
142     final DocIdSetIterator filterIter = filterDocIdSet.iterator();
143     if (filterIter == null) {
144       // this means the filter does not accept any documents.
145       return null;
146     }
147
148     // we are gonna advance() this scorer, so we set inorder=true/toplevel=false
149     final Scorer scorer = weight.scorer(indexReader, true, false);
150     return (scorer == null) ? null : new Scorer(similarity, wrapperWeight) {
151       private int scorerDoc = -1, filterDoc = -1;
152       
153       // optimization: we are topScorer and collect directly using short-circuited algo
154       @Override
155       public void score(Collector collector) throws IOException {
156         int filterDoc = filterIter.nextDoc();
157         int scorerDoc = scorer.advance(filterDoc);
158         // the normalization trick already applies the boost of this query,
159         // so we can use the wrapped scorer directly:
160         collector.setScorer(scorer);
161         for (;;) {
162           if (scorerDoc == filterDoc) {
163             // Check if scorer has exhausted, only before collecting.
164             if (scorerDoc == DocIdSetIterator.NO_MORE_DOCS) {
165               break;
166             }
167             collector.collect(scorerDoc);
168             filterDoc = filterIter.nextDoc();
169             scorerDoc = scorer.advance(filterDoc);
170           } else if (scorerDoc > filterDoc) {
171             filterDoc = filterIter.advance(scorerDoc);
172           } else {
173             scorerDoc = scorer.advance(filterDoc);
174           }
175         }
176       }
177       
178       private int advanceToNextCommonDoc() throws IOException {
179         for (;;) {
180           if (scorerDoc < filterDoc) {
181             scorerDoc = scorer.advance(filterDoc);
182           } else if (scorerDoc == filterDoc) {
183             return scorerDoc;
184           } else {
185             filterDoc = filterIter.advance(scorerDoc);
186           }
187         }
188       }
189
190       @Override
191       public int nextDoc() throws IOException {
192         filterDoc = filterIter.nextDoc();
193         return advanceToNextCommonDoc();
194       }
195       
196       @Override
197       public int advance(int target) throws IOException {
198         if (target > filterDoc) {
199           filterDoc = filterIter.advance(target);
200         }
201         return advanceToNextCommonDoc();
202       }
203
204       @Override
205       public int docID() {
206         return scorerDoc;
207       }
208       
209       @Override
210       public float score() throws IOException {
211         return scorer.score();
212       }
213     };
214   }
215
216   /** Rewrites the wrapped query. */
217   @Override
218   public Query rewrite(IndexReader reader) throws IOException {
219     Query rewritten = query.rewrite(reader);
220     if (rewritten != query) {
221       FilteredQuery clone = (FilteredQuery)this.clone();
222       clone.query = rewritten;
223       return clone;
224     } else {
225       return this;
226     }
227   }
228
229   public Query getQuery() {
230     return query;
231   }
232
233   public Filter getFilter() {
234     return filter;
235   }
236
237   // inherit javadoc
238   @Override
239   public void extractTerms(Set<Term> terms) {
240       getQuery().extractTerms(terms);
241   }
242
243   /** Prints a user-readable version of this query. */
244   @Override
245   public String toString (String s) {
246     StringBuilder buffer = new StringBuilder();
247     buffer.append("filtered(");
248     buffer.append(query.toString(s));
249     buffer.append(")->");
250     buffer.append(filter);
251     buffer.append(ToStringUtils.boost(getBoost()));
252     return buffer.toString();
253   }
254
255   /** Returns true iff <code>o</code> is equal to this. */
256   @Override
257   public boolean equals(Object o) {
258     if (o instanceof FilteredQuery) {
259       FilteredQuery fq = (FilteredQuery) o;
260       return (query.equals(fq.query) && filter.equals(fq.filter) && getBoost()==fq.getBoost());
261     }
262     return false;
263   }
264
265   /** Returns a hash code value for this object. */
266   @Override
267   public int hashCode() {
268     return query.hashCode() ^ filter.hashCode() + Float.floatToRawIntBits(getBoost());
269   }
270 }