content: 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 + ")";
    }
  }
  