1 package org.apache.lucene.search;
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 import org.apache.lucene.index.IndexReader;
21 import org.apache.lucene.index.Term;
22 import org.apache.lucene.util.ToStringUtils;
24 import java.io.IOException;
29 * A query that applies a filter to the results of another query.
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.
35 * <p>Created: Apr 20, 2004 8:58:29 AM
38 * @see CachingWrapperFilter
40 public class FilteredQuery
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>.
52 public FilteredQuery (Query query, Filter filter) {
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.
62 public Weight createWeight(final Searcher searcher) throws IOException {
63 final Weight weight = query.createWeight (searcher);
64 final Similarity similarity = query.getSimilarity(searcher);
68 // pass these methods through to enclosed query's weight
70 public float getValue() { return value; }
73 public boolean scoresDocsOutOfOrder() {
77 public float sumOfSquaredWeights() throws IOException {
78 return weight.sumOfSquaredWeights() * getBoost() * getBoost(); // boost sub-weight
82 public void normalize (float v) {
83 weight.normalize(v * getBoost()); // incorporate boost
84 value = weight.getValue();
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();
96 if (docIdSetIterator.advance(i) == i) {
99 Explanation result = new Explanation
100 (0.0f, "failure to match filter: " + f.toString());
101 result.addDetail(inner);
108 public Query getQuery() { return FilteredQuery.this; }
110 // return a filtering scorer
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);
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
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;
136 final DocIdSet filterDocIdSet = filter.getDocIdSet(indexReader);
137 if (filterDocIdSet == null) {
138 // this means the filter does not accept any documents.
142 final DocIdSetIterator filterIter = filterDocIdSet.iterator();
143 if (filterIter == null) {
144 // this means the filter does not accept any documents.
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;
153 // optimization: we are topScorer and collect directly using short-circuited algo
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);
162 if (scorerDoc == filterDoc) {
163 // Check if scorer has exhausted, only before collecting.
164 if (scorerDoc == DocIdSetIterator.NO_MORE_DOCS) {
167 collector.collect(scorerDoc);
168 filterDoc = filterIter.nextDoc();
169 scorerDoc = scorer.advance(filterDoc);
170 } else if (scorerDoc > filterDoc) {
171 filterDoc = filterIter.advance(scorerDoc);
173 scorerDoc = scorer.advance(filterDoc);
178 private int advanceToNextCommonDoc() throws IOException {
180 if (scorerDoc < filterDoc) {
181 scorerDoc = scorer.advance(filterDoc);
182 } else if (scorerDoc == filterDoc) {
185 filterDoc = filterIter.advance(scorerDoc);
191 public int nextDoc() throws IOException {
192 filterDoc = filterIter.nextDoc();
193 return advanceToNextCommonDoc();
197 public int advance(int target) throws IOException {
198 if (target > filterDoc) {
199 filterDoc = filterIter.advance(target);
201 return advanceToNextCommonDoc();
210 public float score() throws IOException {
211 return scorer.score();
216 /** Rewrites the wrapped query. */
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;
229 public Query getQuery() {
233 public Filter getFilter() {
239 public void extractTerms(Set<Term> terms) {
240 getQuery().extractTerms(terms);
243 /** Prints a user-readable version of this query. */
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();
255 /** Returns true iff <code>o</code> is equal to this. */
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());
265 /** Returns a hash code value for this object. */
267 public int hashCode() {
268 return query.hashCode() ^ filter.hashCode() + Float.floatToRawIntBits(getBoost());