1 package org.apache.lucene.search;
2
3 /**
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import org.apache.lucene.document.Document;
25 import org.apache.lucene.document.FieldSelector;
26 import org.apache.lucene.index.CorruptIndexException;
27 import org.apache.lucene.index.IndexReader;
28 import org.apache.lucene.index.Term;
29 import org.apache.lucene.store.Directory;
30 import org.apache.lucene.util.ReaderUtil;
31
32 /** Implements search over a single IndexReader.
33 *
34 * <p>Applications usually need only call the inherited
35 * {@link #search(Query,int)}
36 * or {@link #search(Query,Filter,int)} methods. For performance reasons it is
37 * recommended to open only one IndexSearcher and use it for all of your searches.
38 *
39 * <a name="thread-safety"></a><p><b>NOTE</b>: {@link
40 * <code>IndexSearcher</code>} instances are completely
41 * thread safe, meaning multiple threads can call any of its
42 * methods, concurrently. If your application requires
43 * external synchronization, you should <b>not</b>
44 * synchronize on the <code>IndexSearcher</code> instance;
45 * use your own (non-Lucene) objects instead.</p>
46 */
47 public class IndexSearcher extends Searcher {
48 IndexReader reader;
49 private boolean closeReader;
50
51 // NOTE: these members might change in incompatible ways
52 // in the next release
53 protected IndexReader[] subReaders;
54 protected int[] docStarts;
55
56 /** Creates a searcher searching the index in the named
57 * directory, with readOnly=true
58 * @throws CorruptIndexException if the index is corrupt
59 * @throws IOException if there is a low-level IO error
60 * @param path directory where IndexReader will be opened
61 */
62 public IndexSearcher(Directory path) throws CorruptIndexException, IOException {
63 this(IndexReader.open(path, true), true);
64 }
65
66 /** Creates a searcher searching the index in the named
67 * directory. You should pass readOnly=true, since it
68 * gives much better concurrent performance, unless you
69 * intend to do write operations (delete documents or
70 * change norms) with the underlying IndexReader.
71 * @throws CorruptIndexException if the index is corrupt
72 * @throws IOException if there is a low-level IO error
73 * @param path directory where IndexReader will be opened
74 * @param readOnly if true, the underlying IndexReader
75 * will be opened readOnly
76 */
77 public IndexSearcher(Directory path, boolean readOnly) throws CorruptIndexException, IOException {
78 this(IndexReader.open(path, readOnly), true);
79 }
80
81 /** Creates a searcher searching the provided index. */
82 public IndexSearcher(IndexReader r) {
83 this(r, false);
84 }
85
86 /** Expert: directly specify the reader, subReaders and
87 * their docID starts.
88 *
89 * <p><b>NOTE:</b> This API is experimental and
90 * might change in incompatible ways in the next
91 * release.</font></p> */
92 public IndexSearcher(IndexReader reader, IndexReader[] subReaders, int[] docStarts) {
93 this.reader = reader;
94 this.subReaders = subReaders;
95 this.docStarts = docStarts;
96 closeReader = false;
97 }
98
99 private IndexSearcher(IndexReader r, boolean closeReader) {
100 reader = r;
101 this.closeReader = closeReader;
102
103 List<IndexReader> subReadersList = new ArrayList<IndexReader>();
104 gatherSubReaders(subReadersList, reader);
105 subReaders = subReadersList.toArray(new IndexReader[subReadersList.size()]);
106 docStarts = new int[subReaders.length];
107 int maxDoc = 0;
108 for (int i = 0; i < subReaders.length; i++) {
109 docStarts[i] = maxDoc;
110 maxDoc += subReaders[i].maxDoc();
111 }
112 }
113
114 protected void gatherSubReaders(List<IndexReader> allSubReaders, IndexReader r) {
115 ReaderUtil.gatherSubReaders(allSubReaders, r);
116 }
117
118 /** Return the {@link IndexReader} this searches. */
119 public IndexReader getIndexReader() {
120 return reader;
121 }
122
123 /**
124 * Note that the underlying IndexReader is not closed, if
125 * IndexSearcher was constructed with IndexSearcher(IndexReader r).
126 * If the IndexReader was supplied implicitly by specifying a directory, then
127 * the IndexReader gets closed.
128 */
129 @Override
130 public void close() throws IOException {
131 if(closeReader)
132 reader.close();
133 }
134
135 // inherit javadoc
136 @Override
137 public int docFreq(Term term) throws IOException {
138 return reader.docFreq(term);
139 }
140
141 // inherit javadoc
142 @Override
143 public Document doc(int i) throws CorruptIndexException, IOException {
144 return reader.document(i);
145 }
146
147 // inherit javadoc
148 @Override
149 public Document doc(int i, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
150 return reader.document(i, fieldSelector);
151 }
152
153 // inherit javadoc
154 @Override
155 public int maxDoc() throws IOException {
156 return reader.maxDoc();
157 }
158
159 // inherit javadoc
160 @Override
161 public TopDocs search(Weight weight, Filter filter, final int nDocs) throws IOException {
162
163 if (nDocs <= 0) {
164 throw new IllegalArgumentException("nDocs must be > 0");
165 }
166
167 TopScoreDocCollector collector = TopScoreDocCollector.create(nDocs, !weight.scoresDocsOutOfOrder());
168 search(weight, filter, collector);
169 return collector.topDocs();
170 }
171
172 @Override
173 public TopFieldDocs search(Weight weight, Filter filter,
174 final int nDocs, Sort sort) throws IOException {
175 return search(weight, filter, nDocs, sort, true);
176 }
177
178 /**
179 * Just like {@link #search(Weight, Filter, int, Sort)}, but you choose
180 * whether or not the fields in the returned {@link FieldDoc} instances should
181 * be set by specifying fillFields.
182 *
183 * <p>NOTE: this does not compute scores by default. If you
184 * need scores, create a {@link TopFieldCollector}
185 * instance by calling {@link TopFieldCollector#create} and
186 * then pass that to {@link #search(Weight, Filter,
187 * Collector)}.</p>
188 */
189 public TopFieldDocs search(Weight weight, Filter filter, final int nDocs,
190 Sort sort, boolean fillFields)
191 throws IOException {
192 TopFieldCollector collector = TopFieldCollector.create(sort, nDocs,
193 fillFields, fieldSortDoTrackScores, fieldSortDoMaxScore, !weight.scoresDocsOutOfOrder());
194 search(weight, filter, collector);
195 return (TopFieldDocs) collector.topDocs();
196 }
197
198 @Override
199 public void search(Weight weight, Filter filter, Collector collector)
200 throws IOException {
201
202 if (filter == null) {
203 for (int i = 0; i < subReaders.length; i++) { // search each subreader
204 collector.setNextReader(subReaders[i], docStarts[i]);
205 Scorer scorer = weight.scorer(subReaders[i], !collector.acceptsDocsOutOfOrder(), true);
206 if (scorer != null) {
207 scorer.score(collector);
208 }
209 }
210 } else {
211 for (int i = 0; i < subReaders.length; i++) { // search each subreader
212 collector.setNextReader(subReaders[i], docStarts[i]);
213 searchWithFilter(subReaders[i], weight, filter, collector);
214 }
215 }
216 }
217
218 private void searchWithFilter(IndexReader reader, Weight weight,
219 final Filter filter, final Collector collector) throws IOException {
220
221 assert filter != null;
222
223 Scorer scorer = weight.scorer(reader, true, false);
224 if (scorer == null) {
225 return;
226 }
227
228 int docID = scorer.docID();
229 assert docID == -1 || docID == DocIdSetIterator.NO_MORE_DOCS;
230
231 // CHECKME: use ConjunctionScorer here?
232 DocIdSet filterDocIdSet = filter.getDocIdSet(reader);
233 if (filterDocIdSet == null) {
234 // this means the filter does not accept any documents.
235 return;
236 }
237
238 DocIdSetIterator filterIter = filterDocIdSet.iterator();
239 if (filterIter == null) {
240 // this means the filter does not accept any documents.
241 return;
242 }
243 int filterDoc = filterIter.nextDoc();
244 int scorerDoc = scorer.advance(filterDoc);
245
246 collector.setScorer(scorer);
247 while (true) {
248 if (scorerDoc == filterDoc) {
249 // Check if scorer has exhausted, only before collecting.
250 if (scorerDoc == DocIdSetIterator.NO_MORE_DOCS) {
251 break;
252 }
253 collector.collect(scorerDoc);
254 filterDoc = filterIter.nextDoc();
255 scorerDoc = scorer.advance(filterDoc);
256 } else if (scorerDoc > filterDoc) {
257 filterDoc = filterIter.advance(scorerDoc);
258 } else {
259 scorerDoc = scorer.advance(filterDoc);
260 }
261 }
262 }
263
264 @Override
265 public Query rewrite(Query original) throws IOException {
266 Query query = original;
267 for (Query rewrittenQuery = query.rewrite(reader); rewrittenQuery != query;
268 rewrittenQuery = query.rewrite(reader)) {
269 query = rewrittenQuery;
270 }
271 return query;
272 }
273
274 @Override
275 public Explanation explain(Weight weight, int doc) throws IOException {
276 int n = ReaderUtil.subIndex(doc, docStarts);
277 int deBasedDoc = doc - docStarts[n];
278
279 return weight.explain(subReaders[n], deBasedDoc);
280 }
281
282 private boolean fieldSortDoTrackScores;
283 private boolean fieldSortDoMaxScore;
284
285 /** By default, no scores are computed when sorting by
286 * field (using {@link #search(Query,Filter,int,Sort)}).
287 * You can change that, per IndexSearcher instance, by
288 * calling this method. Note that this will incur a CPU
289 * cost.
290 *
291 * @param doTrackScores If true, then scores are
292 * returned for every matching document in {@link
293 * TopFieldDocs}.
294 *
295 * @param doMaxScore If true, then the max score for all
296 * matching docs is computed. */
297 public void setDefaultFieldSortScoring(boolean doTrackScores, boolean doMaxScore) {
298 fieldSortDoTrackScores = doTrackScores;
299 fieldSortDoMaxScore = doMaxScore;
300 }
301 }