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 }