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  
35  /**
36   * A Lucene {@link Query} implementation that is used to represent a NOT expression of another wrapped Query object. This query
37   * implementation works by using the {@link Query#weight(Searcher) weight} and
38   * {@link Weight#scorer(IndexReader, boolean, boolean) scorer} of the wrapped query to score (and return) only those documents
39   * that were <i>not</i> scored by the wrapped query. In other words, if the wrapped query ended up scoring any document, that
40   * document is <i>not</i> scored (i.e., skipped) by this query.
41   */
42  public class NotQuery extends Query {
43  
44      private static final long serialVersionUID = 1L;
45  
46      /**
47       * The operand that is being negated by this query.
48       */
49      protected final Query operand;
50  
51      /**
52       * Construct a NOT(x) constraint where the 'x' operand is supplied.
53       * 
54       * @param operand the operand being negated
55       */
56      public NotQuery( Query operand ) {
57          this.operand = operand;
58      }
59  
60      /**
61       * {@inheritDoc}
62       * 
63       * @see org.apache.lucene.search.Query#createWeight(org.apache.lucene.search.Searcher)
64       */
65      @Override
66      public Weight createWeight( Searcher searcher ) {
67          return new NotWeight(searcher);
68      }
69  
70      /**
71       * {@inheritDoc}
72       * 
73       * @see org.apache.lucene.search.Query#clone()
74       */
75      @Override
76      public Object clone() {
77          return new NotQuery(operand);
78      }
79  
80      /**
81       * {@inheritDoc}
82       * 
83       * @see org.apache.lucene.search.Query#toString(java.lang.String)
84       */
85      @Override
86      public String toString( String field ) {
87          return "NOT(" + operand.toString(field) + ")";
88      }
89  
90      /**
91       * Calculates query weights and builds query scores for our NOT queries.
92       */
93      protected class NotWeight extends Weight {
94          private static final long serialVersionUID = 1L;
95          private final Searcher searcher;
96  
97          protected NotWeight( Searcher searcher ) {
98              this.searcher = searcher;
99              assert this.searcher != null;
100         }
101 
102         /**
103          * {@inheritDoc}
104          * 
105          * @see org.apache.lucene.search.Weight#getQuery()
106          */
107         @Override
108         public Query getQuery() {
109             return NotQuery.this;
110         }
111 
112         /**
113          * {@inheritDoc}
114          * <p>
115          * This implementation always returns a weight factor of 1.0.
116          * </p>
117          * 
118          * @see org.apache.lucene.search.Weight#getValue()
119          */
120         @Override
121         public float getValue() {
122             return 1.0f; // weight factor of 1.0
123         }
124 
125         /**
126          * {@inheritDoc}
127          * <p>
128          * This implementation always returns a normalization factor of 1.0.
129          * </p>
130          * 
131          * @see org.apache.lucene.search.Weight#sumOfSquaredWeights()
132          */
133         @Override
134         public float sumOfSquaredWeights() {
135             return 1.0f; // normalization factor of 1.0
136         }
137 
138         /**
139          * {@inheritDoc}
140          * <p>
141          * This implementation always does nothing, as there is nothing to normalize.
142          * </p>
143          * 
144          * @see org.apache.lucene.search.Weight#normalize(float)
145          */
146         @Override
147         public void normalize( float norm ) {
148             // No need to do anything here
149         }
150 
151         /**
152          * {@inheritDoc}
153          * 
154          * @see org.apache.lucene.search.Weight#scorer(org.apache.lucene.index.IndexReader, boolean, boolean)
155          */
156         @Override
157         public Scorer scorer( IndexReader reader,
158                               boolean scoreDocsInOrder,
159                               boolean topScorer ) throws IOException {
160             // Get the operand's score, and set this on the NOT query
161             Scorer operandScorer = operand.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
162             // Return a custom scorer ...
163             return new NotScorer(operandScorer, reader);
164         }
165 
166         /**
167          * {@inheritDoc}
168          * 
169          * @see org.apache.lucene.search.Weight#explain(org.apache.lucene.index.IndexReader, int)
170          */
171         @Override
172         public Explanation explain( IndexReader reader,
173                                     int doc ) throws IOException {
174             Explanation operandExplanation = operand.weight(searcher).explain(reader, doc);
175             String desc = operandExplanation.getDescription();
176             return new Explanation(getValue(), "NOT(" + desc + ")");
177         }
178     }
179 
180     /**
181      * A scorer for the NOT query that iterates over documents (in increasing docId order), using the given scorer implementation
182      * for the operand of the NOT.
183      */
184     protected static class NotScorer extends Scorer {
185         private int docId = -1;
186         private int nextScoredDocId = -1;
187         private final Scorer operandScorer;
188         private final IndexReader reader;
189         private final int pastMaxDocId;
190 
191         /**
192          * @param operandScorer the scorer that is used to score the documents based upon the operand of the NOT; may not be null
193          * @param reader the reader that has access to all the docs ...
194          */
195         protected NotScorer( Scorer operandScorer,
196                              IndexReader reader ) {
197             // We don't care which Similarity we have, because we don't use it. So get the default.
198             super(Similarity.getDefault());
199             this.operandScorer = operandScorer;
200             this.reader = reader;
201             assert this.operandScorer != null;
202             assert this.reader != null;
203             this.pastMaxDocId = this.reader.maxDoc();
204         }
205 
206         /**
207          * {@inheritDoc}
208          * 
209          * @see org.apache.lucene.search.DocIdSetIterator#docID()
210          */
211         @Override
212         public int docID() {
213             return docId;
214         }
215 
216         /**
217          * {@inheritDoc}
218          * 
219          * @see org.apache.lucene.search.DocIdSetIterator#nextDoc()
220          */
221         @Override
222         public int nextDoc() throws IOException {
223             if (nextScoredDocId == -1) {
224                 // Find the first document that is scored by the operand's scorer ...
225                 nextScoredDocId = operandScorer.nextDoc();
226             }
227             do {
228                 ++docId;
229                 if (docId == pastMaxDocId) {
230                     // We're aleady to the end of the documents in the index, so return no more docs
231                     return Scorer.NO_MORE_DOCS;
232                 }
233                 if (docId == nextScoredDocId) {
234                     // Find the next document that is scored by the operand's scorer ...
235                     nextScoredDocId = operandScorer.nextDoc();
236                     continue;
237                 }
238                 if (reader.isDeleted(docId)) {
239                     // We should skip this document ...
240                     continue;
241                 }
242                 return docId;
243             } while (true);
244         }
245 
246         /**
247          * {@inheritDoc}
248          * 
249          * @see org.apache.lucene.search.DocIdSetIterator#advance(int)
250          */
251         @Override
252         public int advance( int target ) throws IOException {
253             if (target == Scorer.NO_MORE_DOCS) return target;
254             while (true) {
255                 int doc = nextDoc();
256                 if (doc >= target) return doc;
257             }
258         }
259 
260         /**
261          * {@inheritDoc}
262          * <p>
263          * This method always returns a score of 1.0 for the current document, since only those documents that satisfy the NOT are
264          * scored by this scorer.
265          * </p>
266          * 
267          * @see org.apache.lucene.search.Scorer#score()
268          */
269         @Override
270         public float score() {
271             return 1.0f;
272         }
273     }
274 }