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 org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.util.ToStringUtils; import java.io.IOException; import java.util.Set; /** * A query that applies a filter to the results of another query. * * <p>Note: the bits are retrieved from the filter each time this * query is used in a search - use a CachingWrapperFilter to avoid * regenerating the bits every time. * * <p>Created: Apr 20, 2004 8:58:29 AM * * @since 1.4 * @see CachingWrapperFilter */ public class FilteredQuery extends Query { Query query; Filter filter; /** * Constructs a new query which applies a filter to the results of the original query. * Filter.getDocIdSet() will be called every time this query is used in a search. * @param query Query to be filtered, cannot be <code>null</code>. * @param filter Filter to apply to query results, cannot be <code>null</code>. */ public FilteredQuery (Query query, Filter filter) { this.query = query; this.filter = filter; } /** * Returns a Weight that applies the filter to the enclosed query's Weight. * This is accomplished by overriding the Scorer returned by the Weight. */ @Override public Weight createWeight(final Searcher searcher) throws IOException { final Weight weight = query.createWeight (searcher); final Similarity similarity = query.getSimilarity(searcher); return new Weight() { private float value; // pass these methods through to enclosed query's weight @Override public float getValue() { return value; } @Override public float sumOfSquaredWeights() throws IOException { return weight.sumOfSquaredWeights() * getBoost() * getBoost(); } @Override public void normalize (float v) { weight.normalize(v); value = weight.getValue() * getBoost(); } @Override public Explanation explain (IndexReader ir, int i) throws IOException { Explanation inner = weight.explain (ir, i); if (getBoost()!=1) { Explanation preBoost = inner; inner = new Explanation(inner.getValue()*getBoost(),"product of:"); inner.addDetail(new Explanation(getBoost(),"boost")); inner.addDetail(preBoost); } Filter f = FilteredQuery.this.filter; DocIdSet docIdSet = f.getDocIdSet(ir); DocIdSetIterator docIdSetIterator = docIdSet == null ? DocIdSet.EMPTY_DOCIDSET.iterator() : docIdSet.iterator(); if (docIdSetIterator == null) { docIdSetIterator = DocIdSet.EMPTY_DOCIDSET.iterator(); } if (docIdSetIterator.advance(i) == i) { return inner; } else { Explanation result = new Explanation (0.0f, "failure to match filter: " + f.toString()); result.addDetail(inner); return result; } } // return this query @Override public Query getQuery() { return FilteredQuery.this; } // return a filtering scorer @Override public Scorer scorer(IndexReader indexReader, boolean scoreDocsInOrder, boolean topScorer) throws IOException { final Scorer scorer = weight.scorer(indexReader, true, false); if (scorer == null) { return null; } DocIdSet docIdSet = filter.getDocIdSet(indexReader); if (docIdSet == null) { return null; } final DocIdSetIterator docIdSetIterator = docIdSet.iterator(); if (docIdSetIterator == null) { return null; } return new Scorer(similarity, this) { private int doc = -1; private int advanceToCommon(int scorerDoc, int disiDoc) throws IOException { while (scorerDoc != disiDoc) { if (scorerDoc < disiDoc) { scorerDoc = scorer.advance(disiDoc); } else { disiDoc = docIdSetIterator.advance(scorerDoc); } } return scorerDoc; } @Override public int nextDoc() throws IOException { int scorerDoc, disiDoc; return doc = (disiDoc = docIdSetIterator.nextDoc()) != NO_MORE_DOCS && (scorerDoc = scorer.nextDoc()) != NO_MORE_DOCS && advanceToCommon(scorerDoc, disiDoc) != NO_MORE_DOCS ? scorer.docID() : NO_MORE_DOCS; } @Override public int docID() { return doc; } @Override public int advance(int target) throws IOException { int disiDoc, scorerDoc; return doc = (disiDoc = docIdSetIterator.advance(target)) != NO_MORE_DOCS && (scorerDoc = scorer.advance(disiDoc)) != NO_MORE_DOCS && advanceToCommon(scorerDoc, disiDoc) != NO_MORE_DOCS ? scorer.docID() : NO_MORE_DOCS; } @Override public float score() throws IOException { return getBoost() * scorer.score(); } }; } }; } /** Rewrites the wrapped query. */ @Override public Query rewrite(IndexReader reader) throws IOException { Query rewritten = query.rewrite(reader); if (rewritten != query) { FilteredQuery clone = (FilteredQuery)this.clone(); clone.query = rewritten; return clone; } else { return this; } } public Query getQuery() { return query; } public Filter getFilter() { return filter; } // inherit javadoc @Override public void extractTerms(Set<Term> terms) { getQuery().extractTerms(terms); } /** Prints a user-readable version of this query. */ @Override public String toString (String s) { StringBuilder buffer = new StringBuilder(); buffer.append("filtered("); buffer.append(query.toString(s)); buffer.append(")->"); buffer.append(filter); buffer.append(ToStringUtils.boost(getBoost())); return buffer.toString(); } /** Returns true iff <code>o</code> is equal to this. */ @Override public boolean equals(Object o) { if (o instanceof FilteredQuery) { FilteredQuery fq = (FilteredQuery) o; return (query.equals(fq.query) && filter.equals(fq.filter) && getBoost()==fq.getBoost()); } return false; } /** Returns a hash code value for this object. */ @Override public int hashCode() { return query.hashCode() ^ filter.hashCode() + Float.floatToRawIntBits(getBoost()); } }