package org.apache.lucene.search; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.lucene.document.Document; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.store.Directory; import org.apache.lucene.store.NIOFSDirectory; // javadocs import org.apache.lucene.util.ReaderUtil; import org.apache.lucene.util.ThreadInterruptedException; /** Implements search over a single IndexReader. * * <p>Applications usually need only call the inherited * {@link #search(Query,int)} * or {@link #search(Query,Filter,int)} methods. For * performance reasons, if your index is unchanging, you * should share a single IndexSearcher instance across * multiple searches instead of creating a new one * per-search. If your index has changed and you wish to * see the changes reflected in searching, you should * use {@link IndexReader#openIfChanged} to obtain a new reader and * then create a new IndexSearcher from that. Also, for * low-latency turnaround it's best to use a near-real-time * reader ({@link IndexReader#open(IndexWriter,boolean)}). * Once you have a new {@link IndexReader}, it's relatively * cheap to create a new IndexSearcher from it. * * <a name="thread-safety"></a><p><b>NOTE</b>: <code>{@link * IndexSearcher}</code> instances are completely * thread safe, meaning multiple threads can call any of its * methods, concurrently. If your application requires * external synchronization, you should <b>not</b> * synchronize on the <code>IndexSearcher</code> instance; * use your own (non-Lucene) objects instead.</p> */ public class IndexSearcher extends Searcher { IndexReader reader; private boolean closeReader; // NOTE: these members might change in incompatible ways // in the next release protected final IndexReader[] subReaders; protected final int[] docStarts; // These are only used for multi-threaded search private final ExecutorService executor; protected final IndexSearcher[] subSearchers; private final int docBase; /** Creates a searcher searching the index in the named * directory, with readOnly=true * @param path directory where IndexReader will be opened * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error * @deprecated use {@link IndexSearcher#IndexSearcher(IndexReader)} instead. */ @Deprecated public IndexSearcher(Directory path) throws CorruptIndexException, IOException { this(IndexReader.open(path, true), true, null); } /** Creates a searcher searching the index in the named * directory. You should pass readOnly=true, since it * gives much better concurrent performance, unless you * intend to do write operations (delete documents or * change norms) with the underlying IndexReader. * @param path directory where IndexReader will be opened * @param readOnly if true, the underlying IndexReader * will be opened readOnly * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error * @deprecated Use {@link IndexSearcher#IndexSearcher(IndexReader)} instead. */ @Deprecated public IndexSearcher(Directory path, boolean readOnly) throws CorruptIndexException, IOException { this(IndexReader.open(path, readOnly), true, null); } /** Creates a searcher searching the provided index. */ public IndexSearcher(IndexReader r) { this(r, false, null); } /** Runs searches for each segment separately, using the * provided ExecutorService. IndexSearcher will not * shutdown/awaitTermination this ExecutorService on * close; you must do so, eventually, on your own. NOTE: * if you are using {@link NIOFSDirectory}, do not use * the shutdownNow method of ExecutorService as this uses * Thread.interrupt under-the-hood which can silently * close file descriptors (see <a * href="https://issues.apache.org/jira/browse/LUCENE-2239">LUCENE-2239</a>). * * @lucene.experimental */ public IndexSearcher(IndexReader r, ExecutorService executor) { this(r, false, executor); } /** Expert: directly specify the reader, subReaders and * their docID starts. * * @lucene.experimental */ public IndexSearcher(IndexReader reader, IndexReader[] subReaders, int[] docStarts) { this(reader, subReaders, docStarts, null); } // Used only when we are an atomic sub-searcher in a parent // IndexSearcher that has an ExecutorService, to record // our docBase in the parent IndexSearcher: private IndexSearcher(IndexReader r, int docBase) { reader = r; this.executor = null; closeReader = false; this.docBase = docBase; subReaders = new IndexReader[] {r}; docStarts = new int[] {0}; subSearchers = null; } /** Expert: directly specify the reader, subReaders and * their docID starts, and an ExecutorService. In this * case, each segment will be separately searched using the * ExecutorService. IndexSearcher will not * shutdown/awaitTermination this ExecutorService on * close; you must do so, eventually, on your own. NOTE: * if you are using {@link NIOFSDirectory}, do not use * the shutdownNow method of ExecutorService as this uses * Thread.interrupt under-the-hood which can silently * close file descriptors (see <a * href="https://issues.apache.org/jira/browse/LUCENE-2239">LUCENE-2239</a>). * * @lucene.experimental */ public IndexSearcher(IndexReader reader, IndexReader[] subReaders, int[] docStarts, ExecutorService executor) { this.reader = reader; this.subReaders = subReaders; this.docStarts = docStarts; if (executor == null) { subSearchers = null; } else { subSearchers = new IndexSearcher[subReaders.length]; for(int i=0;i<subReaders.length;i++) { subSearchers[i] = new IndexSearcher(subReaders[i], docStarts[i]); } } closeReader = false; this.executor = executor; docBase = 0; } private IndexSearcher(IndexReader r, boolean closeReader, ExecutorService executor) { reader = r; this.executor = executor; this.closeReader = closeReader; List<IndexReader> subReadersList = new ArrayList<IndexReader>(); gatherSubReaders(subReadersList, reader); subReaders = subReadersList.toArray(new IndexReader[subReadersList.size()]); docStarts = new int[subReaders.length]; int maxDoc = 0; for (int i = 0; i < subReaders.length; i++) { docStarts[i] = maxDoc; maxDoc += subReaders[i].maxDoc(); } if (executor == null) { subSearchers = null; } else { subSearchers = new IndexSearcher[subReaders.length]; for (int i = 0; i < subReaders.length; i++) { subSearchers[i] = new IndexSearcher(subReaders[i], docStarts[i]); } } docBase = 0; } protected void gatherSubReaders(List<IndexReader> allSubReaders, IndexReader r) { ReaderUtil.gatherSubReaders(allSubReaders, r); } /** Return the {@link IndexReader} this searches. */ public IndexReader getIndexReader() { return reader; } /** Returns the atomic subReaders used by this searcher. */ public IndexReader[] getSubReaders() { return subReaders; } /** Expert: Returns one greater than the largest possible document number. * * @see org.apache.lucene.index.IndexReader#maxDoc() */ @Override public int maxDoc() { return reader.maxDoc(); } /** Returns total docFreq for this term. */ @Override public int docFreq(final Term term) throws IOException { if (executor == null) { return reader.docFreq(term); } else { final ExecutionHelper<Integer> runner = new ExecutionHelper<Integer>(executor); for(int i = 0; i < subReaders.length; i++) { final IndexSearcher searchable = subSearchers[i]; runner.submit(new Callable<Integer>() { public Integer call() throws IOException { return Integer.valueOf(searchable.docFreq(term)); } }); } int docFreq = 0; for (Integer num : runner) { docFreq += num.intValue(); } return docFreq; } } /* Sugar for .getIndexReader().document(docID) */ @Override public Document doc(int docID) throws CorruptIndexException, IOException { return reader.document(docID); } /* Sugar for .getIndexReader().document(docID, fieldSelector) */ @Override public Document doc(int docID, FieldSelector fieldSelector) throws CorruptIndexException, IOException { return reader.document(docID, fieldSelector); } /** Expert: Set the Similarity implementation used by this Searcher. * * @see Similarity#setDefault(Similarity) */ @Override public void setSimilarity(Similarity similarity) { super.setSimilarity(similarity); } @Override public Similarity getSimilarity() { return super.getSimilarity(); } /** * Note that the underlying IndexReader is not closed, if * IndexSearcher was constructed with IndexSearcher(IndexReader r). * If the IndexReader was supplied implicitly by specifying a directory, then * the IndexReader is closed. */ @Override public void close() throws IOException { if (closeReader) { reader.close(); } } /** Finds the top <code>n</code> * hits for <code>query</code> where all results are after a previous * result (<code>after</code>). * <p> * By passing the bottom result from a previous page as <code>after</code>, * this method can be used for efficient 'deep-paging' across potentially * large result sets. * * @throws BooleanQuery.TooManyClauses */ public TopDocs searchAfter(ScoreDoc after, Query query, int n) throws IOException { return searchAfter(after, query, null, n); } /** Finds the top <code>n</code> * hits for <code>query</code>, applying <code>filter</code> if non-null, * where all results are after a previous result (<code>after</code>). * <p> * By passing the bottom result from a previous page as <code>after</code>, * this method can be used for efficient 'deep-paging' across potentially * large result sets. * * @throws BooleanQuery.TooManyClauses */ public TopDocs searchAfter(ScoreDoc after, Query query, Filter filter, int n) throws IOException { return search(createNormalizedWeight(query), filter, after, n); } /** Finds the top <code>n</code> * hits for <code>query</code>. * * @throws BooleanQuery.TooManyClauses */ @Override public TopDocs search(Query query, int n) throws IOException { return search(query, null, n); } /** Finds the top <code>n</code> * hits for <code>query</code>, applying <code>filter</code> if non-null. * * @throws BooleanQuery.TooManyClauses */ @Override public TopDocs search(Query query, Filter filter, int n) throws IOException { return search(createNormalizedWeight(query), filter, n); } /** Lower-level search API. * * <p>{@link Collector#collect(int)} is called for every matching * document. * <br>Collector-based access to remote indexes is discouraged. * * <p>Applications should only use this if they need <i>all</i> of the * matching documents. The high-level search API ({@link * Searcher#search(Query, Filter, int)}) is usually more efficient, as it skips * non-high-scoring hits. * * @param query to match documents * @param filter if non-null, used to permit documents to be collected. * @param results to receive hits * @throws BooleanQuery.TooManyClauses */ @Override public void search(Query query, Filter filter, Collector results) throws IOException { search(createNormalizedWeight(query), filter, results); } /** Lower-level search API. * * <p>{@link Collector#collect(int)} is called for every matching document. * * <p>Applications should only use this if they need <i>all</i> of the * matching documents. The high-level search API ({@link * Searcher#search(Query, int)}) is usually more efficient, as it skips * non-high-scoring hits. * <p>Note: The <code>score</code> passed to this method is a raw score. * In other words, the score will not necessarily be a float whose value is * between 0 and 1. * @throws BooleanQuery.TooManyClauses */ @Override public void search(Query query, Collector results) throws IOException { search(createNormalizedWeight(query), null, results); } /** Search implementation with arbitrary sorting. Finds * the top <code>n</code> hits for <code>query</code>, applying * <code>filter</code> if non-null, and sorting the hits by the criteria in * <code>sort</code>. * * <p>NOTE: this does not compute scores by default; use * {@link IndexSearcher#setDefaultFieldSortScoring} to * enable scoring. * * @throws BooleanQuery.TooManyClauses */ @Override public TopFieldDocs search(Query query, Filter filter, int n, Sort sort) throws IOException { return search(createNormalizedWeight(query), filter, n, sort); } /** * Search implementation with arbitrary sorting and no filter. * @param query The query to search for * @param n Return only the top n results * @param sort The {@link org.apache.lucene.search.Sort} object * @return The top docs, sorted according to the supplied {@link org.apache.lucene.search.Sort} instance * @throws IOException */ @Override public TopFieldDocs search(Query query, int n, Sort sort) throws IOException { return search(createNormalizedWeight(query), null, n, sort); } /** Expert: Low-level search implementation. Finds the top <code>n</code> * hits for <code>query</code>, applying <code>filter</code> if non-null. * * <p>Applications should usually call {@link Searcher#search(Query,int)} or * {@link Searcher#search(Query,Filter,int)} instead. * @throws BooleanQuery.TooManyClauses */ @Override public TopDocs search(Weight weight, Filter filter, int nDocs) throws IOException { return search(weight, filter, null, nDocs); } /** * Expert: Low-level search implementation. Finds the top <code>n</code> * hits for <code>query</code>, applying <code>filter</code> if non-null, * returning results after <code>after</code>. * * @throws BooleanQuery.TooManyClauses */ protected TopDocs search(Weight weight, Filter filter, ScoreDoc after, int nDocs) throws IOException { if (executor == null) { // single thread int limit = reader.maxDoc(); if (limit == 0) { limit = 1; } nDocs = Math.min(nDocs, limit); TopScoreDocCollector collector = TopScoreDocCollector.create(nDocs, after, !weight.scoresDocsOutOfOrder()); search(weight, filter, collector); return collector.topDocs(); } else { final HitQueue hq = new HitQueue(nDocs, false); final Lock lock = new ReentrantLock(); final ExecutionHelper<TopDocs> runner = new ExecutionHelper<TopDocs>(executor); for (int i = 0; i < subReaders.length; i++) { // search each sub runner.submit( new MultiSearcherCallableNoSort(lock, subSearchers[i], weight, filter, after, nDocs, hq)); } int totalHits = 0; float maxScore = Float.NEGATIVE_INFINITY; double sum = 0.0f; double max = Double.NEGATIVE_INFINITY; double min = Double.POSITIVE_INFINITY; for (final TopDocs topDocs : runner) { if(topDocs.totalHits != 0) { totalHits += topDocs.totalHits; maxScore = Math.max(maxScore, topDocs.getMaxScore()); sum += topDocs.getSum(); max = Math.max(max, topDocs.getMax()); min = Math.min(min, topDocs.getMin()); } } final ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()]; for (int i = hq.size() - 1; i >= 0; i--) // put docs in array scoreDocs[i] = hq.pop(); return new TopDocs(totalHits, scoreDocs, maxScore, sum, max, min); } } /** Expert: Low-level search implementation with arbitrary sorting. Finds * the top <code>n</code> hits for <code>query</code>, applying * <code>filter</code> if non-null, and sorting the hits by the criteria in * <code>sort</code>. * * <p>Applications should usually call {@link * Searcher#search(Query,Filter,int,Sort)} instead. * * @throws BooleanQuery.TooManyClauses */ @Override public TopFieldDocs search(Weight weight, Filter filter, final int nDocs, Sort sort) throws IOException { return search(weight, filter, nDocs, sort, true); } /** * Just like {@link #search(Weight, Filter, int, Sort)}, but you choose * whether or not the fields in the returned {@link FieldDoc} instances should * be set by specifying fillFields. * * <p>NOTE: this does not compute scores by default. If you * need scores, create a {@link TopFieldCollector} * instance by calling {@link TopFieldCollector#create} and * then pass that to {@link #search(Weight, Filter, * Collector)}.</p> */ protected TopFieldDocs search(Weight weight, Filter filter, int nDocs, Sort sort, boolean fillFields) throws IOException { if (sort == null) throw new NullPointerException(); if (executor == null) { // single thread int limit = reader.maxDoc(); if (limit == 0) { limit = 1; } nDocs = Math.min(nDocs, limit); TopFieldCollector collector = TopFieldCollector.create(sort, nDocs, fillFields, fieldSortDoTrackScores, fieldSortDoMaxScore, !weight.scoresDocsOutOfOrder()); search(weight, filter, collector); return (TopFieldDocs) collector.topDocs(); } else { final TopFieldCollector topCollector = TopFieldCollector.create(sort, nDocs, fillFields, fieldSortDoTrackScores, fieldSortDoMaxScore, false); final Lock lock = new ReentrantLock(); final ExecutionHelper<TopFieldDocs> runner = new ExecutionHelper<TopFieldDocs>(executor); for (int i = 0; i < subReaders.length; i++) { // search each sub runner.submit( new MultiSearcherCallableWithSort(lock, subSearchers[i], weight, filter, nDocs, topCollector, sort)); } int totalHits = 0; float maxScore = Float.NEGATIVE_INFINITY; double sum = 0.0f; double max = Double.NEGATIVE_INFINITY; double min = Double.POSITIVE_INFINITY; for (final TopFieldDocs topFieldDocs : runner) { if (topFieldDocs.totalHits != 0) { totalHits += topFieldDocs.totalHits; maxScore = Math.max(maxScore, topFieldDocs.getMaxScore()); sum += topFieldDocs.getSum(); max = Math.max(max, topFieldDocs.getMax()); min = Math.min(min, topFieldDocs.getMin()); } } final TopFieldDocs topDocs = (TopFieldDocs) topCollector.topDocs(); return new TopFieldDocs(totalHits, topDocs.scoreDocs, topDocs.fields, maxScore, sum, max, min); } } /** * Lower-level search API. * * <p> * {@link Collector#collect(int)} is called for every document. <br> * Collector-based access to remote indexes is discouraged. * * <p> * Applications should only use this if they need <i>all</i> of the matching * documents. The high-level search API ({@link Searcher#search(Query,int)}) is * usually more efficient, as it skips non-high-scoring hits. * * @param weight * to match documents * @param filter * if non-null, used to permit documents to be collected. * @param collector * to receive hits * @throws BooleanQuery.TooManyClauses */ @Override public void search(Weight weight, Filter filter, Collector collector) throws IOException { // TODO: should we make this // threaded...? the Collector could be sync'd? // always use single thread: for (int i = 0; i < subReaders.length; i++) { // search each subreader collector.setNextReader(subReaders[i], docBase + docStarts[i]); final Scorer scorer = (filter == null) ? weight.scorer(subReaders[i], !collector.acceptsDocsOutOfOrder(), true) : FilteredQuery.getFilteredScorer(subReaders[i], getSimilarity(), weight, weight, filter); if (scorer != null) { scorer.score(collector); } } } public static interface MdrillCollector{ public void setNextReader(IndexReader reader, int docBase)throws IOException; public void collect(int docid)throws IOException; public boolean isstop()throws IOException; } public void ScoreFind(Query query, Filter filter, MdrillCollector results) throws IOException { ScoreFind(createNormalizedWeight(query), filter, results); } public void ScoreFind(Weight weight, Filter filter, MdrillCollector collector) throws IOException { for (int i = 0; i < subReaders.length; i++) { if(collector.isstop()) { continue; } collector.setNextReader(subReaders[i], docBase + docStarts[i]); final Scorer scorer = (filter == null) ?weight.scorer(subReaders[i], false, true) : FilteredQuery.getFilteredScorer(subReaders[i], getSimilarity(), weight, weight, filter); if(scorer!=null) { int doc; while (!collector.isstop()&&(doc = scorer.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { collector.collect(doc); } } } } /** Expert: called to re-write queries into primitive queries. * @throws BooleanQuery.TooManyClauses */ @Override public Query rewrite(Query original) throws IOException { Query query = original; for (Query rewrittenQuery = query.rewrite(reader); rewrittenQuery != query; rewrittenQuery = query.rewrite(reader)) { query = rewrittenQuery; } return query; } /** Returns an Explanation that describes how <code>doc</code> scored against * <code>query</code>. * * <p>This is intended to be used in developing Similarity implementations, * and, for good performance, should not be displayed with every hit. * Computing an explanation is as expensive as executing the query over the * entire index. */ @Override public Explanation explain(Query query, int doc) throws IOException { return explain(createNormalizedWeight(query), doc); } /** Expert: low-level implementation method * Returns an Explanation that describes how <code>doc</code> scored against * <code>weight</code>. * * <p>This is intended to be used in developing Similarity implementations, * and, for good performance, should not be displayed with every hit. * Computing an explanation is as expensive as executing the query over the * entire index. * <p>Applications should call {@link Searcher#explain(Query, int)}. * @throws BooleanQuery.TooManyClauses */ @Override public Explanation explain(Weight weight, int doc) throws IOException { int n = ReaderUtil.subIndex(doc, docStarts); int deBasedDoc = doc - docStarts[n]; return weight.explain(subReaders[n], deBasedDoc); } private boolean fieldSortDoTrackScores; private boolean fieldSortDoMaxScore; /** By default, no scores are computed when sorting by * field (using {@link #search(Query,Filter,int,Sort)}). * You can change that, per IndexSearcher instance, by * calling this method. Note that this will incur a CPU * cost. * * @param doTrackScores If true, then scores are * returned for every matching document in {@link * TopFieldDocs}. * * @param doMaxScore If true, then the max score for all * matching docs is computed. */ public void setDefaultFieldSortScoring(boolean doTrackScores, boolean doMaxScore) { fieldSortDoTrackScores = doTrackScores; fieldSortDoMaxScore = doMaxScore; if (subSearchers != null) { // propagate settings to subs for (IndexSearcher sub : subSearchers) { sub.setDefaultFieldSortScoring(doTrackScores, doMaxScore); } } } /** * Creates a normalized weight for a top-level {@link Query}. * The query is rewritten by this method and {@link Query#createWeight} called, * afterwards the {@link Weight} is normalized. The returned {@code Weight} * can then directly be used to get a {@link Scorer}. * @lucene.internal */ public Weight createNormalizedWeight(Query query) throws IOException { return super.createNormalizedWeight(query); } /** * A thread subclass for searching a single searchable */ private static final class MultiSearcherCallableNoSort implements Callable<TopDocs> { private final Lock lock; private final IndexSearcher searchable; private final Weight weight; private final Filter filter; private final ScoreDoc after; private final int nDocs; private final HitQueue hq; public MultiSearcherCallableNoSort(Lock lock, IndexSearcher searchable, Weight weight, Filter filter, ScoreDoc after, int nDocs, HitQueue hq) { this.lock = lock; this.searchable = searchable; this.weight = weight; this.filter = filter; this.after = after; this.nDocs = nDocs; this.hq = hq; } public TopDocs call() throws IOException { final TopDocs docs; // we could call the 4-arg method, but we want to invoke the old method // for backwards purposes unless someone is using the new searchAfter. if (after == null) { docs = searchable.search (weight, filter, nDocs); } else { docs = searchable.search (weight, filter, after, nDocs); } final ScoreDoc[] scoreDocs = docs.scoreDocs; //it would be so nice if we had a thread-safe insert lock.lock(); try { for (int j = 0; j < scoreDocs.length; j++) { // merge scoreDocs into hq final ScoreDoc scoreDoc = scoreDocs[j]; if (scoreDoc == hq.insertWithOverflow(scoreDoc)) { break; } } } finally { lock.unlock(); } return docs; } } /** * A thread subclass for searching a single searchable */ private static final class MultiSearcherCallableWithSort implements Callable<TopFieldDocs> { private final Lock lock; private final IndexSearcher searchable; private final Weight weight; private final Filter filter; private final int nDocs; private final TopFieldCollector hq; private final Sort sort; public MultiSearcherCallableWithSort(Lock lock, IndexSearcher searchable, Weight weight, Filter filter, int nDocs, TopFieldCollector hq, Sort sort) { this.lock = lock; this.searchable = searchable; this.weight = weight; this.filter = filter; this.nDocs = nDocs; this.hq = hq; this.sort = sort; } private final class FakeScorer extends Scorer { float score; int doc; public FakeScorer() { super(null, null); } @Override public int advance(int target) { throw new UnsupportedOperationException(); } @Override public int docID() { return doc; } @Override public float freq() { throw new UnsupportedOperationException(); } @Override public int nextDoc() { throw new UnsupportedOperationException(); } @Override public float score() { return score; } } private final FakeScorer fakeScorer = new FakeScorer(); public TopFieldDocs call() throws IOException { final TopFieldDocs docs = searchable.search (weight, filter, nDocs, sort); // If one of the Sort fields is FIELD_DOC, need to fix its values, so that // it will break ties by doc Id properly. Otherwise, it will compare to // 'relative' doc Ids, that belong to two different searchables. for (int j = 0; j < docs.fields.length; j++) { if (docs.fields[j].getType() == SortField.DOC) { // iterate over the score docs and change their fields value for (int j2 = 0; j2 < docs.scoreDocs.length; j2++) { FieldDoc fd = (FieldDoc) docs.scoreDocs[j2]; fd.fields[j] = Integer.valueOf(((Integer) fd.fields[j]).intValue()); } break; } } lock.lock(); try { hq.setNextReader(searchable.getIndexReader(), searchable.docBase); hq.setScorer(fakeScorer); for(ScoreDoc scoreDoc : docs.scoreDocs) { final int docID = scoreDoc.doc - searchable.docBase; fakeScorer.doc = docID; fakeScorer.score = scoreDoc.score; hq.collect(docID); } } finally { lock.unlock(); } return docs; } } /** * A helper class that wraps a {@link CompletionService} and provides an * iterable interface to the completed {@link Callable} instances. * * @param <T> * the type of the {@link Callable} return value */ private static final class ExecutionHelper<T> implements Iterator<T>, Iterable<T> { private final CompletionService<T> service; private int numTasks; ExecutionHelper(final Executor executor) { this.service = new ExecutorCompletionService<T>(executor); } public boolean hasNext() { return numTasks > 0; } public void submit(Callable<T> task) { this.service.submit(task); ++numTasks; } public T next() { if(!this.hasNext()) throw new NoSuchElementException(); try { return service.take().get(); } catch (InterruptedException e) { throw new ThreadInterruptedException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } finally { --numTasks; } } public void remove() { throw new UnsupportedOperationException(); } public Iterator<T> iterator() { // use the shortcut here - this is only used in a private context return this; } } @Override public String toString() { return "IndexSearcher(" + reader + ")"; } }