1
package org.apache.lucene.search;
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
11
* http://www.apache.org/licenses/LICENSE-2.0
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.
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.Iterator;
23
import java.util.List;
24
import java.util.NoSuchElementException;
25
import java.util.concurrent.Callable;
26
import java.util.concurrent.CompletionService;
27
import java.util.concurrent.ExecutionException;
28
import java.util.concurrent.Executor;
29
import java.util.concurrent.ExecutorCompletionService;
30
import java.util.concurrent.ExecutorService;
31
import java.util.concurrent.locks.Lock;
32
import java.util.concurrent.locks.ReentrantLock;
34
import org.apache.lucene.document.Document;
35
import org.apache.lucene.document.FieldSelector;
36
import org.apache.lucene.index.CorruptIndexException;
37
import org.apache.lucene.index.IndexReader;
38
import org.apache.lucene.index.Term;
39
import org.apache.lucene.store.Directory;
40
import org.apache.lucene.store.NIOFSDirectory; // javadocs
41
import org.apache.lucene.util.ReaderUtil;
42
import org.apache.lucene.util.ThreadInterruptedException;
44
/** Implements search over a single IndexReader.
46
* <p>Applications usually need only call the inherited
47
* {@link #search(Query,int)}
48
* or {@link #search(Query,Filter,int)} methods. For
49
* performance reasons, if your index is unchanging, you
50
* should share a single IndexSearcher instance across
51
* multiple searches instead of creating a new one
52
* per-search. If your index has changed and you wish to
53
* see the changes reflected in searching, you should
54
* use {@link IndexReader#openIfChanged} to obtain a new reader and
55
* then create a new IndexSearcher from that. Also, for
56
* low-latency turnaround it's best to use a near-real-time
57
* reader ({@link IndexReader#open(IndexWriter,boolean)}).
58
* Once you have a new {@link IndexReader}, it's relatively
59
* cheap to create a new IndexSearcher from it.
61
* <a name="thread-safety"></a><p><b>NOTE</b>: <code>{@link
62
* IndexSearcher}</code> instances are completely
63
* thread safe, meaning multiple threads can call any of its
64
* methods, concurrently. If your application requires
65
* external synchronization, you should <b>not</b>
66
* synchronize on the <code>IndexSearcher</code> instance;
67
* use your own (non-Lucene) objects instead.</p>
69
public class IndexSearcher extends Searcher {
71
private boolean closeReader;
73
// NOTE: these members might change in incompatible ways
74
// in the next release
75
protected final IndexReader[] subReaders;
76
protected final int[] docStarts;
78
// These are only used for multi-threaded search
79
private final ExecutorService executor;
80
protected final IndexSearcher[] subSearchers;
82
private final int docBase;
84
/** Creates a searcher searching the index in the named
85
* directory, with readOnly=true
86
* @param path directory where IndexReader will be opened
87
* @throws CorruptIndexException if the index is corrupt
88
* @throws IOException if there is a low-level IO error
89
* @deprecated use {@link IndexSearcher#IndexSearcher(IndexReader)} instead.
92
public IndexSearcher(Directory path) throws CorruptIndexException, IOException {
93
this(IndexReader.open(path, true), true, null);
96
/** Creates a searcher searching the index in the named
97
* directory. You should pass readOnly=true, since it
98
* gives much better concurrent performance, unless you
99
* intend to do write operations (delete documents or
100
* change norms) with the underlying IndexReader.
101
* @param path directory where IndexReader will be opened
102
* @param readOnly if true, the underlying IndexReader
103
* will be opened readOnly
104
* @throws CorruptIndexException if the index is corrupt
105
* @throws IOException if there is a low-level IO error
106
* @deprecated Use {@link IndexSearcher#IndexSearcher(IndexReader)} instead.
109
public IndexSearcher(Directory path, boolean readOnly) throws CorruptIndexException, IOException {
110
this(IndexReader.open(path, readOnly), true, null);
113
/** Creates a searcher searching the provided index. */
114
public IndexSearcher(IndexReader r) {
115
this(r, false, null);
118
/** Runs searches for each segment separately, using the
119
* provided ExecutorService. IndexSearcher will not
120
* shutdown/awaitTermination this ExecutorService on
121
* close; you must do so, eventually, on your own. NOTE:
122
* if you are using {@link NIOFSDirectory}, do not use
123
* the shutdownNow method of ExecutorService as this uses
124
* Thread.interrupt under-the-hood which can silently
125
* close file descriptors (see <a
126
* href="https://issues.apache.org/jira/browse/LUCENE-2239">LUCENE-2239</a>).
128
* @lucene.experimental */
129
public IndexSearcher(IndexReader r, ExecutorService executor) {
130
this(r, false, executor);
133
/** Expert: directly specify the reader, subReaders and
134
* their docID starts.
136
* @lucene.experimental */
137
public IndexSearcher(IndexReader reader, IndexReader[] subReaders, int[] docStarts) {
138
this(reader, subReaders, docStarts, null);
141
// Used only when we are an atomic sub-searcher in a parent
142
// IndexSearcher that has an ExecutorService, to record
143
// our docBase in the parent IndexSearcher:
144
private IndexSearcher(IndexReader r, int docBase) {
146
this.executor = null;
148
this.docBase = docBase;
149
subReaders = new IndexReader[] {r};
150
docStarts = new int[] {0};
154
/** Expert: directly specify the reader, subReaders and
155
* their docID starts, and an ExecutorService. In this
156
* case, each segment will be separately searched using the
157
* ExecutorService. IndexSearcher will not
158
* shutdown/awaitTermination this ExecutorService on
159
* close; you must do so, eventually, on your own. NOTE:
160
* if you are using {@link NIOFSDirectory}, do not use
161
* the shutdownNow method of ExecutorService as this uses
162
* Thread.interrupt under-the-hood which can silently
163
* close file descriptors (see <a
164
* href="https://issues.apache.org/jira/browse/LUCENE-2239">LUCENE-2239</a>).
166
* @lucene.experimental */
167
public IndexSearcher(IndexReader reader, IndexReader[] subReaders, int[] docStarts, ExecutorService executor) {
168
this.reader = reader;
169
this.subReaders = subReaders;
170
this.docStarts = docStarts;
171
if (executor == null) {
174
subSearchers = new IndexSearcher[subReaders.length];
175
for(int i=0;i<subReaders.length;i++) {
176
subSearchers[i] = new IndexSearcher(subReaders[i], docStarts[i]);
180
this.executor = executor;
184
private IndexSearcher(IndexReader r, boolean closeReader, ExecutorService executor) {
186
this.executor = executor;
187
this.closeReader = closeReader;
189
List<IndexReader> subReadersList = new ArrayList<IndexReader>();
190
gatherSubReaders(subReadersList, reader);
191
subReaders = subReadersList.toArray(new IndexReader[subReadersList.size()]);
192
docStarts = new int[subReaders.length];
194
for (int i = 0; i < subReaders.length; i++) {
195
docStarts[i] = maxDoc;
196
maxDoc += subReaders[i].maxDoc();
198
if (executor == null) {
201
subSearchers = new IndexSearcher[subReaders.length];
202
for (int i = 0; i < subReaders.length; i++) {
203
subSearchers[i] = new IndexSearcher(subReaders[i], docStarts[i]);
209
protected void gatherSubReaders(List<IndexReader> allSubReaders, IndexReader r) {
210
ReaderUtil.gatherSubReaders(allSubReaders, r);
213
/** Return the {@link IndexReader} this searches. */
214
public IndexReader getIndexReader() {
218
/** Returns the atomic subReaders used by this searcher. */
219
public IndexReader[] getSubReaders() {
223
/** Expert: Returns one greater than the largest possible document number.
225
* @see org.apache.lucene.index.IndexReader#maxDoc()
228
public int maxDoc() {
229
return reader.maxDoc();
232
/** Returns total docFreq for this term. */
234
public int docFreq(final Term term) throws IOException {
235
if (executor == null) {
236
return reader.docFreq(term);
238
final ExecutionHelper<Integer> runner = new ExecutionHelper<Integer>(executor);
239
for(int i = 0; i < subReaders.length; i++) {
240
final IndexSearcher searchable = subSearchers[i];
241
runner.submit(new Callable<Integer>() {
242
public Integer call() throws IOException {
243
return Integer.valueOf(searchable.docFreq(term));
248
for (Integer num : runner) {
249
docFreq += num.intValue();
255
/* Sugar for .getIndexReader().document(docID) */
257
public Document doc(int docID) throws CorruptIndexException, IOException {
258
return reader.document(docID);
261
/* Sugar for .getIndexReader().document(docID, fieldSelector) */
263
public Document doc(int docID, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
264
return reader.document(docID, fieldSelector);
267
/** Expert: Set the Similarity implementation used by this Searcher.
269
* @see Similarity#setDefault(Similarity)
272
public void setSimilarity(Similarity similarity) {
273
super.setSimilarity(similarity);
277
public Similarity getSimilarity() {
278
return super.getSimilarity();
282
* Note that the underlying IndexReader is not closed, if
283
* IndexSearcher was constructed with IndexSearcher(IndexReader r).
284
* If the IndexReader was supplied implicitly by specifying a directory, then
285
* the IndexReader is closed.
288
public void close() throws IOException {
294
/** Finds the top <code>n</code>
295
* hits for <code>query</code> where all results are after a previous
296
* result (<code>after</code>).
298
* By passing the bottom result from a previous page as <code>after</code>,
299
* this method can be used for efficient 'deep-paging' across potentially
302
* @throws BooleanQuery.TooManyClauses
304
public TopDocs searchAfter(ScoreDoc after, Query query, int n) throws IOException {
305
return searchAfter(after, query, null, n);
308
/** Finds the top <code>n</code>
309
* hits for <code>query</code>, applying <code>filter</code> if non-null,
310
* where all results are after a previous result (<code>after</code>).
312
* By passing the bottom result from a previous page as <code>after</code>,
313
* this method can be used for efficient 'deep-paging' across potentially
316
* @throws BooleanQuery.TooManyClauses
318
public TopDocs searchAfter(ScoreDoc after, Query query, Filter filter, int n) throws IOException {
319
return search(createNormalizedWeight(query), filter, after, n);
322
/** Finds the top <code>n</code>
323
* hits for <code>query</code>.
325
* @throws BooleanQuery.TooManyClauses
328
public TopDocs search(Query query, int n)
330
return search(query, null, n);
334
/** Finds the top <code>n</code>
335
* hits for <code>query</code>, applying <code>filter</code> if non-null.
337
* @throws BooleanQuery.TooManyClauses
340
public TopDocs search(Query query, Filter filter, int n)
342
return search(createNormalizedWeight(query), filter, n);
345
/** Lower-level search API.
347
* <p>{@link Collector#collect(int)} is called for every matching
349
* <br>Collector-based access to remote indexes is discouraged.
351
* <p>Applications should only use this if they need <i>all</i> of the
352
* matching documents. The high-level search API ({@link
353
* Searcher#search(Query, Filter, int)}) is usually more efficient, as it skips
354
* non-high-scoring hits.
356
* @param query to match documents
357
* @param filter if non-null, used to permit documents to be collected.
358
* @param results to receive hits
359
* @throws BooleanQuery.TooManyClauses
362
public void search(Query query, Filter filter, Collector results)
364
search(createNormalizedWeight(query), filter, results);
367
/** Lower-level search API.
369
* <p>{@link Collector#collect(int)} is called for every matching document.
371
* <p>Applications should only use this if they need <i>all</i> of the
372
* matching documents. The high-level search API ({@link
373
* Searcher#search(Query, int)}) is usually more efficient, as it skips
374
* non-high-scoring hits.
375
* <p>Note: The <code>score</code> passed to this method is a raw score.
376
* In other words, the score will not necessarily be a float whose value is
378
* @throws BooleanQuery.TooManyClauses
381
public void search(Query query, Collector results)
383
search(createNormalizedWeight(query), null, results);
386
/** Search implementation with arbitrary sorting. Finds
387
* the top <code>n</code> hits for <code>query</code>, applying
388
* <code>filter</code> if non-null, and sorting the hits by the criteria in
391
* <p>NOTE: this does not compute scores by default; use
392
* {@link IndexSearcher#setDefaultFieldSortScoring} to
395
* @throws BooleanQuery.TooManyClauses
398
public TopFieldDocs search(Query query, Filter filter, int n,
399
Sort sort) throws IOException {
400
return search(createNormalizedWeight(query), filter, n, sort);
404
* Search implementation with arbitrary sorting and no filter.
405
* @param query The query to search for
406
* @param n Return only the top n results
407
* @param sort The {@link org.apache.lucene.search.Sort} object
408
* @return The top docs, sorted according to the supplied {@link org.apache.lucene.search.Sort} instance
409
* @throws IOException
412
public TopFieldDocs search(Query query, int n,
413
Sort sort) throws IOException {
414
return search(createNormalizedWeight(query), null, n, sort);
417
/** Expert: Low-level search implementation. Finds the top <code>n</code>
418
* hits for <code>query</code>, applying <code>filter</code> if non-null.
420
* <p>Applications should usually call {@link Searcher#search(Query,int)} or
421
* {@link Searcher#search(Query,Filter,int)} instead.
422
* @throws BooleanQuery.TooManyClauses
425
public TopDocs search(Weight weight, Filter filter, int nDocs) throws IOException {
426
return search(weight, filter, null, nDocs);
430
* Expert: Low-level search implementation. Finds the top <code>n</code>
431
* hits for <code>query</code>, applying <code>filter</code> if non-null,
432
* returning results after <code>after</code>.
434
* @throws BooleanQuery.TooManyClauses
436
protected TopDocs search(Weight weight, Filter filter, ScoreDoc after, int nDocs) throws IOException {
437
if (executor == null) {
439
int limit = reader.maxDoc();
443
nDocs = Math.min(nDocs, limit);
444
TopScoreDocCollector collector = TopScoreDocCollector.create(nDocs, after, !weight.scoresDocsOutOfOrder());
445
search(weight, filter, collector);
446
return collector.topDocs();
448
final HitQueue hq = new HitQueue(nDocs, false);
449
final Lock lock = new ReentrantLock();
450
final ExecutionHelper<TopDocs> runner = new ExecutionHelper<TopDocs>(executor);
452
for (int i = 0; i < subReaders.length; i++) { // search each sub
454
new MultiSearcherCallableNoSort(lock, subSearchers[i], weight, filter, after, nDocs, hq));
458
float maxScore = Float.NEGATIVE_INFINITY;
459
for (final TopDocs topDocs : runner) {
460
if(topDocs.totalHits != 0) {
461
totalHits += topDocs.totalHits;
462
maxScore = Math.max(maxScore, topDocs.getMaxScore());
466
final ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()];
467
for (int i = hq.size() - 1; i >= 0; i--) // put docs in array
468
scoreDocs[i] = hq.pop();
470
return new TopDocs(totalHits, scoreDocs, maxScore);
474
/** Expert: Low-level search implementation with arbitrary sorting. Finds
475
* the top <code>n</code> hits for <code>query</code>, applying
476
* <code>filter</code> if non-null, and sorting the hits by the criteria in
479
* <p>Applications should usually call {@link
480
* Searcher#search(Query,Filter,int,Sort)} instead.
482
* @throws BooleanQuery.TooManyClauses
485
public TopFieldDocs search(Weight weight, Filter filter,
486
final int nDocs, Sort sort) throws IOException {
487
return search(weight, filter, nDocs, sort, true);
491
* Just like {@link #search(Weight, Filter, int, Sort)}, but you choose
492
* whether or not the fields in the returned {@link FieldDoc} instances should
493
* be set by specifying fillFields.
495
* <p>NOTE: this does not compute scores by default. If you
496
* need scores, create a {@link TopFieldCollector}
497
* instance by calling {@link TopFieldCollector#create} and
498
* then pass that to {@link #search(Weight, Filter,
501
protected TopFieldDocs search(Weight weight, Filter filter, int nDocs,
502
Sort sort, boolean fillFields)
505
if (sort == null) throw new NullPointerException();
507
if (executor == null) {
509
int limit = reader.maxDoc();
513
nDocs = Math.min(nDocs, limit);
515
TopFieldCollector collector = TopFieldCollector.create(sort, nDocs,
516
fillFields, fieldSortDoTrackScores, fieldSortDoMaxScore, !weight.scoresDocsOutOfOrder());
517
search(weight, filter, collector);
518
return (TopFieldDocs) collector.topDocs();
520
final TopFieldCollector topCollector = TopFieldCollector.create(sort, nDocs,
522
fieldSortDoTrackScores,
526
final Lock lock = new ReentrantLock();
527
final ExecutionHelper<TopFieldDocs> runner = new ExecutionHelper<TopFieldDocs>(executor);
528
for (int i = 0; i < subReaders.length; i++) { // search each sub
530
new MultiSearcherCallableWithSort(lock, subSearchers[i], weight, filter, nDocs, topCollector, sort));
533
float maxScore = Float.NEGATIVE_INFINITY;
534
for (final TopFieldDocs topFieldDocs : runner) {
535
if (topFieldDocs.totalHits != 0) {
536
totalHits += topFieldDocs.totalHits;
537
maxScore = Math.max(maxScore, topFieldDocs.getMaxScore());
541
final TopFieldDocs topDocs = (TopFieldDocs) topCollector.topDocs();
543
return new TopFieldDocs(totalHits, topDocs.scoreDocs, topDocs.fields, topDocs.getMaxScore());
548
* Lower-level search API.
551
* {@link Collector#collect(int)} is called for every document. <br>
552
* Collector-based access to remote indexes is discouraged.
555
* Applications should only use this if they need <i>all</i> of the matching
556
* documents. The high-level search API ({@link Searcher#search(Query,int)}) is
557
* usually more efficient, as it skips non-high-scoring hits.
562
* if non-null, used to permit documents to be collected.
565
* @throws BooleanQuery.TooManyClauses
568
public void search(Weight weight, Filter filter, Collector collector)
571
// TODO: should we make this
572
// threaded...? the Collector could be sync'd?
574
// always use single thread:
575
for (int i = 0; i < subReaders.length; i++) { // search each subreader
576
collector.setNextReader(subReaders[i], docBase + docStarts[i]);
577
final Scorer scorer = (filter == null) ?
578
weight.scorer(subReaders[i], !collector.acceptsDocsOutOfOrder(), true) :
579
FilteredQuery.getFilteredScorer(subReaders[i], getSimilarity(), weight, weight, filter);
580
if (scorer != null) {
581
scorer.score(collector);
586
/** Expert: called to re-write queries into primitive queries.
587
* @throws BooleanQuery.TooManyClauses
590
public Query rewrite(Query original) throws IOException {
591
Query query = original;
592
for (Query rewrittenQuery = query.rewrite(reader); rewrittenQuery != query;
593
rewrittenQuery = query.rewrite(reader)) {
594
query = rewrittenQuery;
599
/** Returns an Explanation that describes how <code>doc</code> scored against
600
* <code>query</code>.
602
* <p>This is intended to be used in developing Similarity implementations,
603
* and, for good performance, should not be displayed with every hit.
604
* Computing an explanation is as expensive as executing the query over the
608
public Explanation explain(Query query, int doc) throws IOException {
609
return explain(createNormalizedWeight(query), doc);
612
/** Expert: low-level implementation method
613
* Returns an Explanation that describes how <code>doc</code> scored against
614
* <code>weight</code>.
616
* <p>This is intended to be used in developing Similarity implementations,
617
* and, for good performance, should not be displayed with every hit.
618
* Computing an explanation is as expensive as executing the query over the
620
* <p>Applications should call {@link Searcher#explain(Query, int)}.
621
* @throws BooleanQuery.TooManyClauses
624
public Explanation explain(Weight weight, int doc) throws IOException {
625
int n = ReaderUtil.subIndex(doc, docStarts);
626
int deBasedDoc = doc - docStarts[n];
628
return weight.explain(subReaders[n], deBasedDoc);
631
private boolean fieldSortDoTrackScores;
632
private boolean fieldSortDoMaxScore;
634
/** By default, no scores are computed when sorting by
635
* field (using {@link #search(Query,Filter,int,Sort)}).
636
* You can change that, per IndexSearcher instance, by
637
* calling this method. Note that this will incur a CPU
640
* @param doTrackScores If true, then scores are
641
* returned for every matching document in {@link
644
* @param doMaxScore If true, then the max score for all
645
* matching docs is computed. */
646
public void setDefaultFieldSortScoring(boolean doTrackScores, boolean doMaxScore) {
647
fieldSortDoTrackScores = doTrackScores;
648
fieldSortDoMaxScore = doMaxScore;
649
if (subSearchers != null) { // propagate settings to subs
650
for (IndexSearcher sub : subSearchers) {
651
sub.setDefaultFieldSortScoring(doTrackScores, doMaxScore);
657
* Creates a normalized weight for a top-level {@link Query}.
658
* The query is rewritten by this method and {@link Query#createWeight} called,
659
* afterwards the {@link Weight} is normalized. The returned {@code Weight}
660
* can then directly be used to get a {@link Scorer}.
663
public Weight createNormalizedWeight(Query query) throws IOException {
664
return super.createNormalizedWeight(query);
668
* A thread subclass for searching a single searchable
670
private static final class MultiSearcherCallableNoSort implements Callable<TopDocs> {
672
private final Lock lock;
673
private final IndexSearcher searchable;
674
private final Weight weight;
675
private final Filter filter;
676
private final ScoreDoc after;
677
private final int nDocs;
678
private final HitQueue hq;
680
public MultiSearcherCallableNoSort(Lock lock, IndexSearcher searchable, Weight weight,
681
Filter filter, ScoreDoc after, int nDocs, HitQueue hq) {
683
this.searchable = searchable;
684
this.weight = weight;
685
this.filter = filter;
691
public TopDocs call() throws IOException {
693
// we could call the 4-arg method, but we want to invoke the old method
694
// for backwards purposes unless someone is using the new searchAfter.
696
docs = searchable.search (weight, filter, nDocs);
698
docs = searchable.search (weight, filter, after, nDocs);
700
final ScoreDoc[] scoreDocs = docs.scoreDocs;
701
//it would be so nice if we had a thread-safe insert
704
for (int j = 0; j < scoreDocs.length; j++) { // merge scoreDocs into hq
705
final ScoreDoc scoreDoc = scoreDocs[j];
706
if (scoreDoc == hq.insertWithOverflow(scoreDoc)) {
719
* A thread subclass for searching a single searchable
721
private static final class MultiSearcherCallableWithSort implements Callable<TopFieldDocs> {
723
private final Lock lock;
724
private final IndexSearcher searchable;
725
private final Weight weight;
726
private final Filter filter;
727
private final int nDocs;
728
private final TopFieldCollector hq;
729
private final Sort sort;
731
public MultiSearcherCallableWithSort(Lock lock, IndexSearcher searchable, Weight weight,
732
Filter filter, int nDocs, TopFieldCollector hq, Sort sort) {
734
this.searchable = searchable;
735
this.weight = weight;
736
this.filter = filter;
742
private final class FakeScorer extends Scorer {
746
public FakeScorer() {
751
public int advance(int target) {
752
throw new UnsupportedOperationException();
761
public float freq() {
762
throw new UnsupportedOperationException();
766
public int nextDoc() {
767
throw new UnsupportedOperationException();
771
public float score() {
776
private final FakeScorer fakeScorer = new FakeScorer();
778
public TopFieldDocs call() throws IOException {
779
final TopFieldDocs docs = searchable.search (weight, filter, nDocs, sort);
780
// If one of the Sort fields is FIELD_DOC, need to fix its values, so that
781
// it will break ties by doc Id properly. Otherwise, it will compare to
782
// 'relative' doc Ids, that belong to two different searchables.
783
for (int j = 0; j < docs.fields.length; j++) {
784
if (docs.fields[j].getType() == SortField.DOC) {
785
// iterate over the score docs and change their fields value
786
for (int j2 = 0; j2 < docs.scoreDocs.length; j2++) {
787
FieldDoc fd = (FieldDoc) docs.scoreDocs[j2];
788
fd.fields[j] = Integer.valueOf(((Integer) fd.fields[j]).intValue());
796
hq.setNextReader(searchable.getIndexReader(), searchable.docBase);
797
hq.setScorer(fakeScorer);
798
for(ScoreDoc scoreDoc : docs.scoreDocs) {
799
final int docID = scoreDoc.doc - searchable.docBase;
800
fakeScorer.doc = docID;
801
fakeScorer.score = scoreDoc.score;
813
* A helper class that wraps a {@link CompletionService} and provides an
814
* iterable interface to the completed {@link Callable} instances.
817
* the type of the {@link Callable} return value
819
private static final class ExecutionHelper<T> implements Iterator<T>, Iterable<T> {
820
private final CompletionService<T> service;
821
private int numTasks;
823
ExecutionHelper(final Executor executor) {
824
this.service = new ExecutorCompletionService<T>(executor);
827
public boolean hasNext() {
831
public void submit(Callable<T> task) {
832
this.service.submit(task);
838
throw new NoSuchElementException();
840
return service.take().get();
841
} catch (InterruptedException e) {
842
throw new ThreadInterruptedException(e);
843
} catch (ExecutionException e) {
844
throw new RuntimeException(e);
850
public void remove() {
851
throw new UnsupportedOperationException();
854
public Iterator<T> iterator() {
855
// use the shortcut here - this is only used in a private context
861
public String toString() {
862
return "IndexSearcher(" + reader + ")";