001: package org.apache.lucene.search;
002:
003: /**
004: * Licensed to the Apache Software Foundation (ASF) under one or more
005: * contributor license agreements. See the NOTICE file distributed with
006: * this work for additional information regarding copyright ownership.
007: * The ASF licenses this file to You under the Apache License, Version 2.0
008: * (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: import org.apache.lucene.index.IndexReader;
021: import org.apache.lucene.util.ToStringUtils;
022:
023: import java.io.IOException;
024: import java.util.BitSet;
025: import java.util.Set;
026:
027: /**
028: * A query that applies a filter to the results of another query.
029: *
030: * <p>Note: the bits are retrieved from the filter each time this
031: * query is used in a search - use a CachingWrapperFilter to avoid
032: * regenerating the bits every time.
033: *
034: * <p>Created: Apr 20, 2004 8:58:29 AM
035: *
036: * @author Tim Jones
037: * @since 1.4
038: * @version $Id: FilteredQuery.java 544254 2007-06-04 20:41:06Z doronc $
039: * @see CachingWrapperFilter
040: */
041: public class FilteredQuery extends Query {
042:
043: Query query;
044: Filter filter;
045:
046: /**
047: * Constructs a new query which applies a filter to the results of the original query.
048: * Filter.bits() will be called every time this query is used in a search.
049: * @param query Query to be filtered, cannot be <code>null</code>.
050: * @param filter Filter to apply to query results, cannot be <code>null</code>.
051: */
052: public FilteredQuery(Query query, Filter filter) {
053: this .query = query;
054: this .filter = filter;
055: }
056:
057: /**
058: * Returns a Weight that applies the filter to the enclosed query's Weight.
059: * This is accomplished by overriding the Scorer returned by the Weight.
060: */
061: protected Weight createWeight(final Searcher searcher)
062: throws IOException {
063: final Weight weight = query.createWeight(searcher);
064: final Similarity similarity = query.getSimilarity(searcher);
065: return new Weight() {
066: private float value;
067:
068: // pass these methods through to enclosed query's weight
069: public float getValue() {
070: return value;
071: }
072:
073: public float sumOfSquaredWeights() throws IOException {
074: return weight.sumOfSquaredWeights() * getBoost()
075: * getBoost();
076: }
077:
078: public void normalize(float v) {
079: weight.normalize(v);
080: value = weight.getValue() * getBoost();
081: }
082:
083: public Explanation explain(IndexReader ir, int i)
084: throws IOException {
085: Explanation inner = weight.explain(ir, i);
086: if (getBoost() != 1) {
087: Explanation preBoost = inner;
088: inner = new Explanation(inner.getValue()
089: * getBoost(), "product of:");
090: inner
091: .addDetail(new Explanation(getBoost(),
092: "boost"));
093: inner.addDetail(preBoost);
094: }
095: Filter f = FilteredQuery.this .filter;
096: BitSet matches = f.bits(ir);
097: if (matches.get(i))
098: return inner;
099: Explanation result = new Explanation(0.0f,
100: "failure to match filter: " + f.toString());
101: result.addDetail(inner);
102: return result;
103: }
104:
105: // return this query
106: public Query getQuery() {
107: return FilteredQuery.this ;
108: }
109:
110: // return a filtering scorer
111: public Scorer scorer(IndexReader indexReader)
112: throws IOException {
113: final Scorer scorer = weight.scorer(indexReader);
114: final BitSet bitset = filter.bits(indexReader);
115: return new Scorer(similarity) {
116:
117: public boolean next() throws IOException {
118: do {
119: if (!scorer.next()) {
120: return false;
121: }
122: } while (!bitset.get(scorer.doc()));
123: /* When skipTo() is allowed on scorer it should be used here
124: * in combination with bitset.nextSetBit(...)
125: * See the while loop in skipTo() below.
126: */
127: return true;
128: }
129:
130: public int doc() {
131: return scorer.doc();
132: }
133:
134: public boolean skipTo(int i) throws IOException {
135: if (!scorer.skipTo(i)) {
136: return false;
137: }
138: while (!bitset.get(scorer.doc())) {
139: int nextFiltered = bitset.nextSetBit(scorer
140: .doc() + 1);
141: if (nextFiltered == -1) {
142: return false;
143: } else if (!scorer.skipTo(nextFiltered)) {
144: return false;
145: }
146: }
147: return true;
148: }
149:
150: public float score() throws IOException {
151: return getBoost() * scorer.score();
152: }
153:
154: // add an explanation about whether the document was filtered
155: public Explanation explain(int i)
156: throws IOException {
157: Explanation exp = scorer.explain(i);
158: exp.setValue(getBoost() * exp.getValue());
159:
160: if (bitset.get(i))
161: exp.setDescription("allowed by filter: "
162: + exp.getDescription());
163: else
164: exp.setDescription("removed by filter: "
165: + exp.getDescription());
166: return exp;
167: }
168: };
169: }
170: };
171: }
172:
173: /** Rewrites the wrapped query. */
174: public Query rewrite(IndexReader reader) throws IOException {
175: Query rewritten = query.rewrite(reader);
176: if (rewritten != query) {
177: FilteredQuery clone = (FilteredQuery) this .clone();
178: clone.query = rewritten;
179: return clone;
180: } else {
181: return this ;
182: }
183: }
184:
185: public Query getQuery() {
186: return query;
187: }
188:
189: public Filter getFilter() {
190: return filter;
191: }
192:
193: // inherit javadoc
194: public void extractTerms(Set terms) {
195: getQuery().extractTerms(terms);
196: }
197:
198: /** Prints a user-readable version of this query. */
199: public String toString(String s) {
200: StringBuffer buffer = new StringBuffer();
201: buffer.append("filtered(");
202: buffer.append(query.toString(s));
203: buffer.append(")->");
204: buffer.append(filter);
205: buffer.append(ToStringUtils.boost(getBoost()));
206: return buffer.toString();
207: }
208:
209: /** Returns true iff <code>o</code> is equal to this. */
210: public boolean equals(Object o) {
211: if (o instanceof FilteredQuery) {
212: FilteredQuery fq = (FilteredQuery) o;
213: return (query.equals(fq.query) && filter.equals(fq.filter) && getBoost() == fq
214: .getBoost());
215: }
216: return false;
217: }
218:
219: /** Returns a hash code value for this object. */
220: public int hashCode() {
221: return query.hashCode() ^ filter.hashCode()
222: + Float.floatToRawIntBits(getBoost());
223: }
224: }
|