Index: MultiSearcher.java
===================================================================
--- MultiSearcher.java	(revision 719114)
+++ MultiSearcher.java	(working copy)
@@ -21,10 +21,17 @@
 import org.apache.lucene.document.FieldSelector;
 import org.apache.lucene.index.CorruptIndexException;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.util.PriorityQueue;
 
 import java.io.IOException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -196,58 +203,46 @@
 
   public TopDocs search(Weight weight, Filter filter, int nDocs)
   throws IOException {
-
-    HitQueue hq = new HitQueue(nDocs);
     int totalHits = 0;
-
+    List scoreDocResults = new ArrayList(searchables.length);
     for (int i = 0; i < searchables.length; i++) { // search each searcher
       TopDocs docs = searchables[i].search(weight, filter, nDocs);
       totalHits += docs.totalHits;		  // update totalHits
       ScoreDoc[] scoreDocs = docs.scoreDocs;
+      List scoreDocList = new ArrayList(scoreDocs.length);
       for (int j = 0; j < scoreDocs.length; j++) { // merge scoreDocs into hq
-	ScoreDoc scoreDoc = scoreDocs[j];
-        scoreDoc.doc += starts[i];                // convert doc
-        if(!hq.insert(scoreDoc))
-            break;                                // no more scores > minScore
+	    ScoreDoc scoreDoc = scoreDocs[j];
+        scoreDoc.doc += starts[i];                // convert doc num
+        scoreDocList.add(scoreDoc);
       }
+      scoreDocResults.add(scoreDocList.iterator());
     }
-
-    ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()];
-    for (int i = hq.size()-1; i >= 0; i--)	  // put docs in array
-      scoreDocs[i] = (ScoreDoc)hq.pop();
-    
+    ArrayList mergedList = mergeResult(0, nDocs, scoreDocResults, new ScoreDocComparator());
+    ScoreDoc[] scoreDocs = (ScoreDoc[])mergedList.toArray(new ScoreDoc[mergedList.size()]);
     float maxScore = (totalHits==0) ? Float.NEGATIVE_INFINITY : scoreDocs[0].score;
-    
     return new TopDocs(totalHits, scoreDocs, maxScore);
   }
 
   public TopFieldDocs search (Weight weight, Filter filter, int n, Sort sort)
   throws IOException {
-    FieldDocSortedHitQueue hq = null;
     int totalHits = 0;
-
     float maxScore=Float.NEGATIVE_INFINITY;
-    
+    List scoreDocResults = new ArrayList(searchables.length);
     for (int i = 0; i < searchables.length; i++) { // search each searcher
-      TopFieldDocs docs = searchables[i].search (weight, filter, n, sort);
-      
-      if (hq == null) hq = new FieldDocSortedHitQueue (docs.fields, n);
+      TopFieldDocs docs = searchables[i].search (weight, filter, n, sort);      
       totalHits += docs.totalHits;		  // update totalHits
       maxScore = Math.max(maxScore, docs.getMaxScore());
-      ScoreDoc[] scoreDocs = docs.scoreDocs;
-      for (int j = 0; j < scoreDocs.length; j++) { // merge scoreDocs into hq
+      ScoreDoc[] scoreDocs = docs.scoreDocs; 
+      List scoreDocList = new ArrayList(scoreDocs.length); // if an array iterator is used, the scoreDocList does not need to be created
+      for (int j = 0; j < scoreDocs.length; j++) {
         ScoreDoc scoreDoc = scoreDocs[j];
-        scoreDoc.doc += starts[i];                // convert doc
-        if (!hq.insert (scoreDoc))
-          break;                                  // no more scores > minScore
+        scoreDoc.doc += starts[i];                // convert doc num
+        scoreDocList.add(scoreDoc);
       }
+      scoreDocResults.add(scoreDocList.iterator());
     }
-
-    ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()];
-    for (int i = hq.size() - 1; i >= 0; i--)	  // put docs in array
-      scoreDocs[i] = (ScoreDoc) hq.pop();
-
-    return new TopFieldDocs (totalHits, scoreDocs, hq.getFields(), maxScore);
+    ArrayList mergedList = mergeResult(0, n, scoreDocResults, new FieldDocComparator(sort.getSort()));
+    return new TopFieldDocs (totalHits, (ScoreDoc[])mergedList.toArray(new ScoreDoc[mergedList.size()]), sort.getSort(), maxScore);
   }
 
 
@@ -325,5 +320,292 @@
 
     return rewrittenQuery.weight(cacheSim);
   }
+  
+  
+  public static class MergedIterator implements Iterator
+  {
+    private class IteratorCtx
+    {
+      public Iterator _iterator;
+      public ScoreDoc _curVal;
+      
+      public IteratorCtx(Iterator iterator)
+      {
+        _iterator = iterator;
+        _curVal = null;
+      }
+        
+      public boolean fetch()
+      {
+        if(_iterator.hasNext())
+        {
+          _curVal = (ScoreDoc)_iterator.next();
+          return true;
+        }
+        _curVal = null;
+        return false;
+      }
+    }
 
+    private final PriorityQueue _queue;
+
+    public MergedIterator(final List sources, final Comparator comparator)
+    {
+      _queue = new PriorityQueue()
+      {
+        {
+          this.initialize(sources.size());
+        }
+
+        protected boolean lessThan(Object o1, Object o2)
+        {
+          ScoreDoc v1 = ((IteratorCtx)o1)._curVal;
+          ScoreDoc v2 = ((IteratorCtx)o2)._curVal;
+          
+          return (comparator.compare(v1, v2) < 0);
+        }
+      };    
+      for (int x=0; x < sources.size(); x++)
+      {
+    	Iterator it = (Iterator)sources.get(x);
+        IteratorCtx ctx = new IteratorCtx(it);
+        if(ctx.fetch()) _queue.insert(ctx);
+      }
+    }
+
+    public boolean hasNext()
+    {
+      return _queue.size() > 0;
+    }
+
+    public Object next()
+    {
+      IteratorCtx ctx = (IteratorCtx)_queue.top();
+      ScoreDoc val = ctx._curVal;
+      if (ctx.fetch())
+      {
+        _queue.adjustTop();
+      }
+      else
+      {
+        _queue.pop();
+      }
+      return val;
+    }
+    
+    public void remove()
+    {
+      throw new UnsupportedOperationException();
+    }
+  }
+  
+  public static Iterator mergeIterator(List results, Comparator comparator)
+  {
+    return new MergedIterator(results, comparator);
+  }
+
+  public static ArrayList mergeResult(int offset,int count,List results,Comparator comparator)
+  {
+    Iterator mergedIter=mergeIterator(results, comparator);
+    
+    for (int c = 0; c < offset && mergedIter.hasNext(); c++)
+    {
+      mergedIter.next();
+    }
+    
+    ArrayList mergedList=new ArrayList();
+    
+    for (int c = 0; c < count && mergedIter.hasNext(); c++)
+    {
+      mergedList.add(mergedIter.next());
+    }
+    
+    return mergedList;
+  }
+  
+  public static class ScoreDocComparator implements Comparator {
+	public int compare(Object o1, Object o2) {
+	  ScoreDoc docA = (ScoreDoc)o1;
+	  ScoreDoc docB = (ScoreDoc)o2;
+	  
+	  if (docA.score == docB.score)
+	    if (docA.doc > docB.doc) return 1;
+	    else return -1;
+	  else
+	    if (docA.score < docB.score) return 1;
+	    else return -1;
+    }
+  }
+  
+  /**
+   * Comparator implementation adapted from org.apache.lucene.search.FieldDocSortedHitQueue
+   */
+  public static class FieldDocComparator implements Comparator
+  {
+    volatile SortField[] fields;
+    volatile Collator[]  collators;
+
+    public FieldDocComparator(SortField[] fields)
+    {
+      this.fields = fields;
+      this.collators = hasCollators(fields);
+    }
+
+    /**
+     * Returns an array of collators, possibly <code>null</code>. The collators correspond
+     * to any SortFields which were given a specific locale.
+     * 
+     * @param fields
+     *          Array of sort fields.
+     * @return Array, possibly <code>null</code>.
+     */
+    private Collator[] hasCollators(final SortField[] fields)
+    {
+      if (fields == null)
+        return null;
+      Collator[] ret = new Collator[fields.length];
+      for (int i = 0; i < fields.length; ++i)
+      {
+        Locale locale = fields[i].getLocale();
+        if (locale != null)
+          ret[i] = Collator.getInstance(locale);
+      }
+      return ret;
+    }
+
+    public int compare(Object o1, Object o2)
+    {
+      FieldDoc docA = (FieldDoc)o1; 
+      FieldDoc docB = (FieldDoc)o2;
+      final int n = fields.length;
+      int c = 0;
+      for (int i = 0; i < n && c == 0; ++i)
+      {
+        final int type = fields[i].getType();
+        switch (type)
+        {
+        case SortField.SCORE:
+        {
+          float r1 = ((Float) docA.fields[i]).floatValue();
+          float r2 = ((Float) docB.fields[i]).floatValue();
+          if (r1 > r2)
+            c = -1;
+          if (r1 < r2)
+            c = 1;
+          break;
+        }
+        case SortField.DOC:
+        case SortField.INT:
+        {
+          int i1 = ((Integer) docA.fields[i]).intValue();
+          int i2 = ((Integer) docB.fields[i]).intValue();
+          if (i1 < i2)
+            c = -1;
+          if (i1 > i2)
+            c = 1;
+          break;
+        }
+        case SortField.LONG:
+        {
+          long l1 = ((Long) docA.fields[i]).longValue();
+          long l2 = ((Long) docB.fields[i]).longValue();
+          if (l1 < l2)
+            c = -1;
+          if (l1 > l2)
+            c = 1;
+          break;
+        }
+        case SortField.STRING:
+        {
+          String s1 = (String) docA.fields[i];
+          String s2 = (String) docB.fields[i];
+          // null values need to be sorted first, because of how
+          // FieldCache.getStringIndex()
+          // works - in that routine, any documents without a value in the given field are
+          // put first. If both are null, the next SortField is used
+          if (s1 == null)
+            c = (s2 == null) ? 0 : -1;
+          else if (s2 == null)
+            c = 1; // 
+          else if (fields[i].getLocale() == null)
+          {
+            c = s1.compareTo(s2);
+          }
+          else
+          {
+            c = collators[i].compare(s1, s2);
+          }
+          break;
+        }
+        case SortField.FLOAT:
+        {
+          float f1 = ((Float) docA.fields[i]).floatValue();
+          float f2 = ((Float) docB.fields[i]).floatValue();
+          if (f1 < f2)
+            c = -1;
+          if (f1 > f2)
+            c = 1;
+          break;
+        }
+        case SortField.DOUBLE:
+        {
+          double d1 = ((Double) docA.fields[i]).doubleValue();
+          double d2 = ((Double) docB.fields[i]).doubleValue();
+          if (d1 < d2)
+            c = -1;
+          if (d1 > d2)
+            c = 1;
+          break;
+        }
+        case SortField.BYTE:
+        {
+          int i1 = ((Byte) docA.fields[i]).byteValue();
+          int i2 = ((Byte) docB.fields[i]).byteValue();
+          if (i1 < i2)
+            c = -1;
+          if (i1 > i2)
+            c = 1;
+          break;
+        }
+        case SortField.SHORT:
+        {
+          int i1 = ((Short) docA.fields[i]).shortValue();
+          int i2 = ((Short) docB.fields[i]).shortValue();
+          if (i1 < i2)
+            c = -1;
+          if (i1 > i2)
+            c = 1;
+          break;
+        }
+        case SortField.CUSTOM:
+        {
+          c = docA.fields[i].compareTo(docB.fields[i]);
+          break;
+        }
+        case SortField.AUTO:
+        {
+          // we cannot handle this - even if we determine the type of object (Float or
+          // Integer), we don't necessarily know how to compare them (both SCORE and
+          // FLOAT contain floats, but are sorted opposite of each other). Before
+          // we get here, each AUTO should have been replaced with its actual value.
+          throw new RuntimeException("FieldDocSortedHitQueue cannot use an AUTO SortField");
+        }
+        default:
+        {
+          throw new RuntimeException("invalid SortField type: " + type);
+        }
+        }
+        if (fields[i].getReverse())
+        {
+          c = -c;
+        }
+      }
+
+      // avoid random sort order that could lead to duplicates (bug #31241):
+      if (c == 0)
+        if (docA.doc > docB.doc) return -1;
+        else return 1;
+      return c;
+    }
+  }
 }
