View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.search.lucene.query;
25  
26  import java.io.IOException;
27  import org.apache.lucene.index.IndexReader;
28  import org.apache.lucene.search.Explanation;
29  import org.apache.lucene.search.Query;
30  import org.apache.lucene.search.Scorer;
31  import org.apache.lucene.search.Searcher;
32  import org.apache.lucene.search.Similarity;
33  import org.apache.lucene.search.Weight;
34  import org.modeshape.graph.query.model.FullTextSearchScore;
35  
36  /**
37   * A Lucene {@link Query} implementation that is used to apply a {@link FullTextSearchScore} criteria a NOT expression of another
38   * wrapped Query object. This query implementation works by using the {@link Query#weight(Searcher) weight} and
39   * {@link Weight#scorer(IndexReader, boolean, boolean) scorer} of the wrapped query to score (and return) only those documents
40   * that were <i>not</i> scored by the wrapped query. In other words, if the wrapped query ended up scoring any document, that
41   * document is <i>not</i> scored (i.e., skipped) by this query.
42   */
43  public class ScoreQuery extends Query {
44  
45      private static final long serialVersionUID = 1L;
46  
47      /**
48       * The operand that is being negated by this query.
49       */
50      protected final Query operand;
51  
52      /**
53       * Construct a NOT(x) constraint where the 'x' operand is supplied.
54       * 
55       * @param operand the operand being negated
56       */
57      public ScoreQuery( Query operand ) {
58          this.operand = operand;
59      }
60  
61      /**
62       * {@inheritDoc}
63       * 
64       * @see org.apache.lucene.search.Query#createWeight(org.apache.lucene.search.Searcher)
65       */
66      @Override
67      public Weight createWeight( Searcher searcher ) {
68          return new NotWeight(searcher);
69      }
70  
71      /**
72       * {@inheritDoc}
73       * 
74       * @see org.apache.lucene.search.Query#clone()
75       */
76      @Override
77      public Object clone() {
78          return new ScoreQuery(operand);
79      }
80  
81      /**
82       * {@inheritDoc}
83       * 
84       * @see org.apache.lucene.search.Query#toString(java.lang.String)
85       */
86      @Override
87      public String toString( String field ) {
88          return "NOT(" + operand.toString(field) + ")";
89      }
90  
91      /**
92       * Calculates query weights and builds query scores for our NOT queries.
93       */
94      protected class NotWeight extends Weight {
95          private static final long serialVersionUID = 1L;
96          private final Searcher searcher;
97  
98          protected NotWeight( Searcher searcher ) {
99              this.searcher = searcher;
100             assert this.searcher != null;
101         }
102 
103         /**
104          * {@inheritDoc}
105          * 
106          * @see org.apache.lucene.search.Weight#getQuery()
107          */
108         @Override
109         public Query getQuery() {
110             return ScoreQuery.this;
111         }
112 
113         /**
114          * {@inheritDoc}
115          * <p>
116          * This implementation always returns a weight factor of 1.0.
117          * </p>
118          * 
119          * @see org.apache.lucene.search.Weight#getValue()
120          */
121         @Override
122         public float getValue() {
123             return 1.0f; // weight factor of 1.0
124         }
125 
126         /**
127          * {@inheritDoc}
128          * <p>
129          * This implementation always returns a normalization factor of 1.0.
130          * </p>
131          * 
132          * @see org.apache.lucene.search.Weight#sumOfSquaredWeights()
133          */
134         @Override
135         public float sumOfSquaredWeights() {
136             return 1.0f; // normalization factor of 1.0
137         }
138 
139         /**
140          * {@inheritDoc}
141          * <p>
142          * This implementation always does nothing, as there is nothing to normalize.
143          * </p>
144          * 
145          * @see org.apache.lucene.search.Weight#normalize(float)
146          */
147         @Override
148         public void normalize( float norm ) {
149             // No need to do anything here
150         }
151 
152         /**
153          * {@inheritDoc}
154          * 
155          * @see org.apache.lucene.search.Weight#scorer(org.apache.lucene.index.IndexReader, boolean, boolean)
156          */
157         @Override
158         public Scorer scorer( IndexReader reader,
159                               boolean scoreDocsInOrder,
160                               boolean topScorer ) throws IOException {
161             // Get the operand's score, and set this on the NOT query
162             Scorer operandScorer = operand.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
163             // Return a custom scorer ...
164             return new NotScorer(operandScorer, reader);
165         }
166 
167         /**
168          * {@inheritDoc}
169          * 
170          * @see org.apache.lucene.search.Weight#explain(org.apache.lucene.index.IndexReader, int)
171          */
172         @Override
173         public Explanation explain( IndexReader reader,
174                                     int doc ) throws IOException {
175             Explanation operandExplanation = operand.weight(searcher).explain(reader, doc);
176             String desc = operandExplanation.getDescription();
177             return new Explanation(getValue(), "NOT(" + desc + ")");
178         }
179     }
180 
181     /**
182      * A scorer for the NOT query that iterates over documents (in increasing docId order), using the given scorer implementation
183      * for the operand of the NOT.
184      */
185     protected static class NotScorer extends Scorer {
186         private int docId = -1;
187         private int nextScoredDocId = -1;
188         private final Scorer operandScorer;
189         private final IndexReader reader;
190         private final int pastMaxDocId;
191 
192         /**
193          * @param operandScorer the scorer that is used to score the documents based upon the operand of the NOT; may not be null
194          * @param reader the reader that has access to all the docs ...
195          */
196         protected NotScorer( Scorer operandScorer,
197                              IndexReader reader ) {
198             // We don't care which Similarity we have, because we don't use it. So get the default.
199             super(Similarity.getDefault());
200             this.operandScorer = operandScorer;
201             this.reader = reader;
202             assert this.operandScorer != null;
203             assert this.reader != null;
204             this.pastMaxDocId = this.reader.maxDoc();
205         }
206 
207         /**
208          * {@inheritDoc}
209          * 
210          * @see org.apache.lucene.search.DocIdSetIterator#docID()
211          */
212         @Override
213         public int docID() {
214             return docId;
215         }
216 
217         /**
218          * {@inheritDoc}
219          * 
220          * @see org.apache.lucene.search.DocIdSetIterator#nextDoc()
221          */
222         @Override
223         public int nextDoc() throws IOException {
224             if (nextScoredDocId == -1) {
225                 // Find the first document that is scored by the operand's scorer ...
226                 nextScoredDocId = operandScorer.nextDoc();
227             }
228             do {
229                 ++docId;
230                 if (docId == pastMaxDocId) {
231                     // We're aleady to the end of the documents in the index, so return no more docs
232                     return Scorer.NO_MORE_DOCS;
233                 }
234                 if (docId == nextScoredDocId) {
235                     // Find the next document that is scored by the operand's scorer ...
236                     nextScoredDocId = operandScorer.nextDoc();
237                     continue;
238                 }
239                 if (reader.isDeleted(docId)) {
240                     // We should skip this document ...
241                     continue;
242                 }
243                 return docId;
244             } while (true);
245         }
246 
247         /**
248          * {@inheritDoc}
249          * 
250          * @see org.apache.lucene.search.DocIdSetIterator#advance(int)
251          */
252         @Override
253         public int advance( int target ) throws IOException {
254             if (target == Scorer.NO_MORE_DOCS) return target;
255             while (true) {
256                 int doc = nextDoc();
257                 if (doc >= target) return doc;
258             }
259         }
260 
261         /**
262          * {@inheritDoc}
263          * <p>
264          * This method always returns a score of 1.0 for the current document, since only those documents that satisfy the NOT are
265          * scored by this scorer.
266          * </p>
267          * 
268          * @see org.apache.lucene.search.Scorer#score()
269          */
270         @Override
271         public float score() {
272             return 1.0f;
273         }
274     }
275 }