Index: lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java	(revision 0)
+++ lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java	(working copy)
@@ -0,0 +1,204 @@
+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.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Field.Index;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+
+// TODO: refactor to a base class, that collects freqs from the scorer tree
+// and test all queries with it
+public class TestBooleanQueryVisitSubscorers extends LuceneTestCase {
+  Analyzer analyzer;
+  IndexReader reader;
+  IndexSearcher searcher;
+  Directory dir;
+  
+  static final String F1 = "title";
+  static final String F2 = "body";
+  
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    analyzer = new MockAnalyzer(random);
+    dir = newDirectory();
+    IndexWriterConfig config = newIndexWriterConfig(TEST_VERSION_CURRENT, analyzer);
+    config.setMergePolicy(newLogMergePolicy()); // we will use docids to validate
+    RandomIndexWriter writer = new RandomIndexWriter(random, dir, config);
+    writer.addDocument(doc("lucene", "lucene is a very popular search engine library"));
+    writer.addDocument(doc("solr", "solr is a very popular search server and is using lucene"));
+    writer.addDocument(doc("nutch", "nutch is an internet search engine with web crawler and is using lucene and hadoop"));
+    reader = writer.getReader();
+    writer.close();
+    searcher = new IndexSearcher(reader);
+  }
+  
+  @Override
+  public void tearDown() throws Exception {
+    reader.close();
+    dir.close();
+    super.tearDown();
+  }
+
+  public void testDisjunctions() throws IOException {
+    BooleanQuery bq = new BooleanQuery();
+    bq.add(new TermQuery(new Term(F1, "lucene")), BooleanClause.Occur.SHOULD);
+    bq.add(new TermQuery(new Term(F2, "lucene")), BooleanClause.Occur.SHOULD);
+    bq.add(new TermQuery(new Term(F2, "search")), BooleanClause.Occur.SHOULD);
+    Map<Integer,Integer> tfs = getDocCounts(searcher, bq);
+    assertEquals(3, tfs.size()); // 3 documents
+    assertEquals(3, tfs.get(0).intValue()); // f1:lucene + f2:lucene + f2:search
+    assertEquals(2, tfs.get(1).intValue()); // f2:search + f2:lucene
+    assertEquals(2, tfs.get(2).intValue()); // f2:search + f2:lucene
+  }
+  
+  public void testNestedDisjunctions() throws IOException {
+    BooleanQuery bq = new BooleanQuery();
+    bq.add(new TermQuery(new Term(F1, "lucene")), BooleanClause.Occur.SHOULD);
+    BooleanQuery bq2 = new BooleanQuery();
+    bq2.add(new TermQuery(new Term(F2, "lucene")), BooleanClause.Occur.SHOULD);
+    bq2.add(new TermQuery(new Term(F2, "search")), BooleanClause.Occur.SHOULD);
+    bq.add(bq2, BooleanClause.Occur.SHOULD);
+    Map<Integer,Integer> tfs = getDocCounts(searcher, bq);
+    assertEquals(3, tfs.size()); // 3 documents
+    assertEquals(3, tfs.get(0).intValue()); // f1:lucene + f2:lucene + f2:search
+    assertEquals(2, tfs.get(1).intValue()); // f2:search + f2:lucene
+    assertEquals(2, tfs.get(2).intValue()); // f2:search + f2:lucene
+  }
+  
+  public void testConjunctions() throws IOException {
+    BooleanQuery bq = new BooleanQuery();
+    bq.add(new TermQuery(new Term(F2, "lucene")), BooleanClause.Occur.MUST);
+    bq.add(new TermQuery(new Term(F2, "is")), BooleanClause.Occur.MUST);
+    Map<Integer,Integer> tfs = getDocCounts(searcher, bq);
+    assertEquals(3, tfs.size()); // 3 documents
+    assertEquals(2, tfs.get(0).intValue()); // f2:lucene + f2:is
+    assertEquals(3, tfs.get(1).intValue()); // f2:is + f2:is + f2:lucene
+    assertEquals(3, tfs.get(2).intValue()); // f2:is + f2:is + f2:lucene
+  }
+  
+  static Document doc(String v1, String v2) {
+    Document doc = new Document();
+    doc.add(new Field(F1, v1, Store.YES, Index.NOT_ANALYZED_NO_NORMS));
+    doc.add(new Field(F2, v2, Store.YES, Index.ANALYZED));
+    return doc;
+  }
+  
+  static Map<Integer,Integer> getDocCounts(IndexSearcher searcher, Query query) throws IOException {
+    MyCollector collector = new MyCollector();
+    searcher.search(query, collector);
+    return collector.docCounts;
+  }
+  
+  static class MyCollector extends Collector {
+    
+    private TopDocsCollector<ScoreDoc> collector;
+    private int docBase;
+
+    public final Map<Integer,Integer> docCounts = new HashMap<Integer,Integer>();
+    private final Set<TermQueryScorer> tqsSet = new HashSet<TermQueryScorer>();
+    private final Scorer.ScorerVisitor<Query, Query, Scorer> visitor = new MockScorerVisitor();
+    
+    MyCollector() {
+      collector = TopScoreDocCollector.create(10, true);
+    }
+
+    @Override
+    public boolean acceptsDocsOutOfOrder() {
+      return false;
+    }
+
+    @Override
+    public void collect(int doc) throws IOException {
+      int freq = 0;
+      for(TermQueryScorer scorer : tqsSet) {
+        if (doc == scorer.scorer.docID()) {
+          freq += scorer.scorer.freq();
+        }
+      }
+      docCounts.put(doc + docBase, freq);
+      collector.collect(doc);
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      this.docBase = docBase;
+      collector.setNextReader(reader, docBase);
+    }
+
+    @Override
+    public void setScorer(Scorer scorer) throws IOException {
+      collector.setScorer(scorer);
+      tqsSet.clear();
+      scorer.visitScorers(visitor);
+    }
+    
+    public TopDocs topDocs(){
+      return collector.topDocs();
+    }
+    
+    public int freq(int doc) throws IOException {
+      return docCounts.get(doc);
+    }
+    
+    private class MockScorerVisitor extends Scorer.ScorerVisitor<Query, Query, Scorer> {
+      
+      @Override
+      public void visitOptional(Query parent, Query child, Scorer scorer) {
+        if (child instanceof TermQuery)
+          tqsSet.add(new TermQueryScorer((TermQuery) child, scorer));
+      }
+
+      @Override
+      public void visitProhibited(Query parent, Query child, Scorer scorer) {
+        if (child instanceof TermQuery)
+          tqsSet.add(new TermQueryScorer((TermQuery) child, scorer));
+      }
+
+      @Override
+      public void visitRequired(Query parent, Query child, Scorer scorer) {
+        if (child instanceof TermQuery)
+          tqsSet.add(new TermQueryScorer((TermQuery) child, scorer));
+      }
+    }
+
+    private static class TermQueryScorer {
+      private TermQuery query;
+      private Scorer scorer;
+      public TermQueryScorer(TermQuery query, Scorer scorer) {
+        this.query = query;
+        this.scorer = scorer;
+      }
+    }
+  }
+}

Property changes on: lucene/core/src/test/org/apache/lucene/search/TestBooleanQueryVisitSubscorers.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/ReqOptSumScorer.java	(working copy)
@@ -80,5 +80,11 @@
     return optScorerDoc == curDoc ? reqScore + optScorer.score() : reqScore;
   }
 
+  @Override
+  public float freq() throws IOException {
+    // we might have deferred advance()
+    score();
+    return (optScorer != null && optScorer.docID() == reqScorer.docID()) ? 2 : 1;
+  }
 }
 
Index: lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java	(working copy)
@@ -128,6 +128,11 @@
     }
 
     @Override
+    public float freq() throws IOException {
+      return 1;
+    }
+
+    @Override
     public int docID() {
       return scorer.docID();
     }
@@ -311,8 +316,8 @@
   }
 
   @Override
-  public float freq() {
-    return coordinator.nrMatchers;
+  public float freq() throws IOException {
+    return countingSumScorer.freq();
   }
 
   @Override
Index: lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java	(working copy)
@@ -102,8 +102,13 @@
   public float score() throws IOException {
     return reqScorer.score(); // reqScorer may be null when next() or skipTo() already return false
   }
-  
+
   @Override
+  public float freq() throws IOException {
+    return reqScorer.freq();
+  }
+
+  @Override
   public int advance(int target) throws IOException {
     if (reqScorer == null) {
       return doc = NO_MORE_DOCS;
Index: lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java	(working copy)
@@ -20,42 +20,20 @@
 import java.util.List;
 import java.io.IOException;
 
-import org.apache.lucene.util.ScorerDocQueue;
-
 /** A Scorer for OR like queries, counterpart of <code>ConjunctionScorer</code>.
  * This Scorer implements {@link Scorer#skipTo(int)} and uses skipTo() on the given Scorers. 
  */
-class DisjunctionSumScorer extends Scorer {
-  /** The number of subscorers. */ 
-  private final int nrScorers;
-  
-  /** The subscorers. */
-  protected final List<Scorer> subScorers;
-  
+class DisjunctionSumScorer extends DisjunctionScorer { 
   /** The minimum number of scorers that should match. */
   private final int minimumNrMatchers;
   
-  /** The scorerDocQueue contains all subscorers ordered by their current doc(),
-   * with the minimum at the top.
-   * <br>The scorerDocQueue is initialized the first time next() or skipTo() is called.
-   * <br>An exhausted scorer is immediately removed from the scorerDocQueue.
-   * <br>If less than the minimumNrMatchers scorers
-   * remain in the scorerDocQueue next() and skipTo() return false.
-   * <p>
-   * After each to call to next() or skipTo()
-   * <code>currentSumScore</code> is the total score of the current matching doc,
-   * <code>nrMatchers</code> is the number of matching scorers,
-   * and all scorers are after the matching doc, or are exhausted.
-   */
-  private ScorerDocQueue scorerDocQueue;
-  
   /** The document number of the current match. */
-  private int currentDoc = -1;
+  private int doc = -1;
 
   /** The number of subscorers that provide the current match. */
   protected int nrMatchers = -1;
 
-  private double currentScore = Float.NaN;
+  private double score = Float.NaN;
   
   /** Construct a <code>DisjunctionScorer</code>.
    * @param weight The weight to be used.
@@ -69,21 +47,16 @@
    * it more efficient to use <code>ConjunctionScorer</code>.
    */
   public DisjunctionSumScorer(Weight weight, List<Scorer> subScorers, int minimumNrMatchers) throws IOException {
-    super(weight);
-    
-    nrScorers = subScorers.size();
+    super(null, weight, subScorers.toArray(new Scorer[subScorers.size()]), subScorers.size());
 
     if (minimumNrMatchers <= 0) {
       throw new IllegalArgumentException("Minimum nr of matchers must be positive");
     }
-    if (nrScorers <= 1) {
+    if (numScorers <= 1) {
       throw new IllegalArgumentException("There must be at least 2 subScorers");
     }
 
     this.minimumNrMatchers = minimumNrMatchers;
-    this.subScorers = subScorers;
-
-    initScorerDocQueue();
   }
   
   /** Construct a <code>DisjunctionScorer</code>, using one as the minimum number
@@ -93,123 +66,73 @@
     this(weight, subScorers, 1);
   }
 
-  /** Called the first time next() or skipTo() is called to
-   * initialize <code>scorerDocQueue</code>.
-   */
-  private void initScorerDocQueue() throws IOException {
-    scorerDocQueue = new ScorerDocQueue(nrScorers);
-    for (Scorer se : subScorers) {
-      if (se.nextDoc() != NO_MORE_DOCS) {
-        scorerDocQueue.insert(se);
-      }
-    }
-  }
-
-  /** Scores and collects all matching documents.
-   * @param collector The collector to which all matching documents are passed through.
-   */
   @Override
-  public void score(Collector collector) throws IOException {
-    collector.setScorer(this);
-    while (nextDoc() != NO_MORE_DOCS) {
-      collector.collect(currentDoc);
-    }
-  }
-
-  /** Expert: Collects matching documents in a range.  Hook for optimization.
-   * Note that {@link #next()} must be called once before this method is called
-   * for the first time.
-   * @param collector The collector to which all matching documents are passed through.
-   * @param max Do not score documents past this.
-   * @return true if more matching documents may remain.
-   */
-  @Override
-  protected boolean score(Collector collector, int max, int firstDocID) throws IOException {
-    // firstDocID is ignored since nextDoc() sets 'currentDoc'
-    collector.setScorer(this);
-    while (currentDoc < max) {
-      collector.collect(currentDoc);
-      if (nextDoc() == NO_MORE_DOCS) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  @Override
   public int nextDoc() throws IOException {
-    if (scorerDocQueue.size() < minimumNrMatchers || !advanceAfterCurrent()) {
-      currentDoc = NO_MORE_DOCS;
-    }
-    return currentDoc;
-  }
-
-  /** Advance all subscorers after the current document determined by the
-   * top of the <code>scorerDocQueue</code>.
-   * Repeat until at least the minimum number of subscorers match on the same
-   * document and all subscorers are after that document or are exhausted.
-   * <br>On entry the <code>scorerDocQueue</code> has at least <code>minimumNrMatchers</code>
-   * available. At least the scorer with the minimum document number will be advanced.
-   * @return true iff there is a match.
-   * <br>In case there is a match, </code>currentDoc</code>, </code>currentSumScore</code>,
-   * and </code>nrMatchers</code> describe the match.
-   *
-   * TODO: Investigate whether it is possible to use skipTo() when
-   * the minimum number of matchers is bigger than one, ie. try and use the
-   * character of ConjunctionScorer for the minimum number of matchers.
-   * Also delay calling score() on the sub scorers until the minimum number of
-   * matchers is reached.
-   * <br>For this, a Scorer array with minimumNrMatchers elements might
-   * hold Scorers at currentDoc that are temporarily popped from scorerQueue.
-   */
-  protected boolean advanceAfterCurrent() throws IOException {
-    do { // repeat until minimum nr of matchers
-      currentDoc = scorerDocQueue.topDoc();
-      currentScore = scorerDocQueue.topScore();
-      nrMatchers = 1;
-      do { // Until all subscorers are after currentDoc
-        if (!scorerDocQueue.topNextAndAdjustElsePop()) {
-          if (scorerDocQueue.size() == 0) {
-            break; // nothing more to advance, check for last match.
+    while(true) {
+      while (subScorers[0].docID() == doc) {
+        if (subScorers[0].nextDoc() != NO_MORE_DOCS) {
+          heapAdjust(0);
+        } else {
+          heapRemoveRoot();
+          if (numScorers < minimumNrMatchers) {
+            return doc = NO_MORE_DOCS;
           }
         }
-        if (scorerDocQueue.topDoc() != currentDoc) {
-          break; // All remaining subscorers are after currentDoc.
-        }
-        currentScore += scorerDocQueue.topScore();
-        nrMatchers++;
-      } while (true);
-      
+      }
+      afterNext();
       if (nrMatchers >= minimumNrMatchers) {
-        return true;
-      } else if (scorerDocQueue.size() < minimumNrMatchers) {
-        return false;
+        break;
       }
-    } while (true);
+    }
+    
+    return doc;
   }
   
+  private void afterNext() throws IOException {
+    final Scorer sub = subScorers[0];
+    doc = sub.docID();
+    score = sub.score();
+    nrMatchers = 1;
+    countMatches(1);
+    countMatches(2);
+  }
+  
+  // TODO: this currently scores, but so did the previous impl
+  // TODO: remove recursion.
+  // TODO: if we separate scoring, out of here, modify this
+  // and afterNext() to terminate when nrMatchers == minimumNrMatchers
+  // then also change freq() to just always compute it from scratch
+  private void countMatches(int root) throws IOException {
+    if (root < numScorers && subScorers[root].docID() == doc) {
+      nrMatchers++;
+      score += subScorers[root].score();
+      countMatches((root<<1)+1);
+      countMatches((root<<1)+2);
+    }
+  }
+  
   /** Returns the score of the current document matching the query.
    * Initially invalid, until {@link #nextDoc()} is called the first time.
    */
   @Override
-  public float score() throws IOException { return (float)currentScore; }
+  public float score() throws IOException { 
+    return (float)score; 
+  }
    
   @Override
   public int docID() {
-    return currentDoc;
+    return doc;
   }
-  
-  /** Returns the number of subscorers matching the current document.
-   * Initially invalid, until {@link #nextDoc()} is called the first time.
-   */
-  public int nrMatchers() {
+
+  @Override
+  public float freq() throws IOException {
     return nrMatchers;
   }
 
   /**
    * Advances to the first match beyond the current whose document number is
    * greater than or equal to a given target. <br>
-   * The implementation uses the skipTo() method on the subscorers.
+   * The implementation uses the advance() method on the subscorers.
    * 
    * @param target
    *          The target document number.
@@ -218,20 +141,24 @@
    */
   @Override
   public int advance(int target) throws IOException {
-    if (scorerDocQueue.size() < minimumNrMatchers) {
-      return currentDoc = NO_MORE_DOCS;
+    if (numScorers == 0) return doc = NO_MORE_DOCS;
+    while (subScorers[0].docID() < target) {
+      if (subScorers[0].advance(target) != NO_MORE_DOCS) {
+        heapAdjust(0);
+      } else {
+        heapRemoveRoot();
+        if (numScorers == 0) {
+          return doc = NO_MORE_DOCS;
+        }
+      }
     }
-    if (target <= currentDoc) {
-      return currentDoc;
+    
+    afterNext();
+
+    if (nrMatchers >= minimumNrMatchers) {
+      return doc;
+    } else {
+      return nextDoc();
     }
-    do {
-      if (scorerDocQueue.topDoc() >= target) {
-        return advanceAfterCurrent() ? currentDoc : (currentDoc = NO_MORE_DOCS);
-      } else if (!scorerDocQueue.topSkipToAndAdjustElsePop(target)) {
-        if (scorerDocQueue.size() < minimumNrMatchers) {
-          return currentDoc = NO_MORE_DOCS;
-        }
-      }
-    } while (true);
   }
 }
Index: lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java	(revision 0)
+++ lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java	(working copy)
@@ -0,0 +1,96 @@
+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.
+ */
+
+/**
+ * Base class for Scorers that score disjunctions.
+ * Currently this just provides helper methods to manage the heap.
+ */
+abstract class DisjunctionScorer extends Scorer {
+  protected final Scorer subScorers[];
+  protected int numScorers;
+  
+  protected DisjunctionScorer(Similarity similarity, Weight weight, Scorer subScorers[], int numScorers) {
+    super(similarity, weight);
+    this.subScorers = subScorers;
+    this.numScorers = numScorers;
+    heapify();
+  }
+  
+  /** 
+   * Organize subScorers into a min heap with scorers generating the earliest document on top.
+   */
+  protected final void heapify() {
+    for (int i = (numScorers >> 1) - 1; i >= 0; i--) {
+      heapAdjust(i);
+    }
+  }
+  
+  /** 
+   * The subtree of subScorers at root is a min heap except possibly for its root element.
+   * Bubble the root down as required to make the subtree a heap.
+   */
+  protected final void heapAdjust(int root) {
+    Scorer scorer = subScorers[root];
+    int doc = scorer.docID();
+    int i = root;
+    while (i <= (numScorers >> 1) - 1) {
+      int lchild = (i << 1) + 1;
+      Scorer lscorer = subScorers[lchild];
+      int ldoc = lscorer.docID();
+      int rdoc = Integer.MAX_VALUE, rchild = (i << 1) + 2;
+      Scorer rscorer = null;
+      if (rchild < numScorers) {
+        rscorer = subScorers[rchild];
+        rdoc = rscorer.docID();
+      }
+      if (ldoc < doc) {
+        if (rdoc < ldoc) {
+          subScorers[i] = rscorer;
+          subScorers[rchild] = scorer;
+          i = rchild;
+        } else {
+          subScorers[i] = lscorer;
+          subScorers[lchild] = scorer;
+          i = lchild;
+        }
+      } else if (rdoc < doc) {
+        subScorers[i] = rscorer;
+        subScorers[rchild] = scorer;
+        i = rchild;
+      } else {
+        return;
+      }
+    }
+  }
+
+  /** 
+   * Remove the root Scorer from subScorers and re-establish it as a heap
+   */
+  protected final void heapRemoveRoot() {
+    if (numScorers == 1) {
+      subScorers[0] = null;
+      numScorers = 0;
+    } else {
+      subScorers[0] = subScorers[numScorers - 1];
+      subScorers[numScorers - 1] = null;
+      --numScorers;
+      heapAdjust(0);
+    }
+  }
+}

Property changes on: lucene/core/src/java/org/apache/lucene/search/DisjunctionScorer.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxScorer.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxScorer.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxScorer.java	(working copy)
@@ -24,11 +24,7 @@
  * by the subquery scorers that generate that document, plus tieBreakerMultiplier times the sum of the scores
  * for the other subqueries that generate the document.
  */
-class DisjunctionMaxScorer extends Scorer {
-
-  /* The scorers for subqueries that have remaining docs, kept as a min heap by number of next doc. */
-  private final Scorer[] subScorers;
-  private int numScorers;
+class DisjunctionMaxScorer extends DisjunctionScorer {
   /* Multiplier applied to non-maximum-scoring subqueries for a document as they are summed into the result. */
   private final float tieBreakerMultiplier;
   private int doc = -1;
@@ -55,16 +51,9 @@
    *          length may be larger than the actual number of scorers.
    */
   public DisjunctionMaxScorer(Weight weight, float tieBreakerMultiplier,
-      Similarity similarity, Scorer[] subScorers, int numScorers) throws IOException {
-    super(similarity, weight);
+      Similarity similarity, Scorer[] subScorers, int numScorers) {
+    super(similarity, weight, subScorers, numScorers);
     this.tieBreakerMultiplier = tieBreakerMultiplier;
-    // The passed subScorers array includes only scorers which have documents
-    // (DisjunctionMaxQuery takes care of that), and their nextDoc() was already
-    // called.
-    this.subScorers = subScorers;
-    this.numScorers = numScorers;
-    
-    heapify();
   }
 
   @Override
@@ -114,6 +103,24 @@
   }
 
   @Override
+  public float freq() throws IOException {
+    int doc = subScorers[0].docID();
+    int size = numScorers;
+    return 1 + freq(1, size, doc) + freq(2, size, doc);
+  }
+  
+  // Recursively iterate all subScorers that generated last doc computing sum and max
+  private int freq(int root, int size, int doc) throws IOException {
+    int freq = 0;
+    if (root < size && subScorers[root].docID() == doc) {
+      freq++;
+      freq += freq((root<<1)+1, size, doc);
+      freq += freq((root<<1)+2, size, doc);
+    }
+    return freq;
+  }
+
+  @Override
   public int advance(int target) throws IOException {
     if (numScorers == 0) return doc = NO_MORE_DOCS;
     while (subScorers[0].docID() < target) {
@@ -129,63 +136,6 @@
     return doc = subScorers[0].docID();
   }
 
-  // Organize subScorers into a min heap with scorers generating the earliest document on top.
-  private void heapify() {
-    for (int i = (numScorers >> 1) - 1; i >= 0; i--) {
-      heapAdjust(i);
-    }
-  }
-
-  /* The subtree of subScorers at root is a min heap except possibly for its root element.
-   * Bubble the root down as required to make the subtree a heap.
-   */
-  private void heapAdjust(int root) {
-    Scorer scorer = subScorers[root];
-    int doc = scorer.docID();
-    int i = root;
-    while (i <= (numScorers >> 1) - 1) {
-      int lchild = (i << 1) + 1;
-      Scorer lscorer = subScorers[lchild];
-      int ldoc = lscorer.docID();
-      int rdoc = Integer.MAX_VALUE, rchild = (i << 1) + 2;
-      Scorer rscorer = null;
-      if (rchild < numScorers) {
-        rscorer = subScorers[rchild];
-        rdoc = rscorer.docID();
-      }
-      if (ldoc < doc) {
-        if (rdoc < ldoc) {
-          subScorers[i] = rscorer;
-          subScorers[rchild] = scorer;
-          i = rchild;
-        } else {
-          subScorers[i] = lscorer;
-          subScorers[lchild] = scorer;
-          i = lchild;
-        }
-      } else if (rdoc < doc) {
-        subScorers[i] = rscorer;
-        subScorers[rchild] = scorer;
-        i = rchild;
-      } else {
-        return;
-      }
-    }
-  }
-
-  // Remove the root Scorer from subScorers and re-establish it as a heap
-  private void heapRemoveRoot() {
-    if (numScorers == 1) {
-      subScorers[0] = null;
-      numScorers = 0;
-    } else {
-      subScorers[0] = subScorers[numScorers - 1];
-      subScorers[numScorers - 1] = null;
-      --numScorers;
-      heapAdjust(0);
-    }
-  }
-
   @Override
   public void visitSubScorers(Query parent, BooleanClause.Occur relationship, ScorerVisitor<Query, Query, Scorer> visitor) {
     super.visitSubScorers(parent, relationship, visitor);
Index: lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java	(working copy)
@@ -147,7 +147,7 @@
       int idx = 0;
       for (Weight w : weights) {
         Scorer subScorer = w.scorer(reader, true, false);
-        if (subScorer != null && subScorer.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
+        if (subScorer != null) {
           scorers[idx++] = subScorer;
         }
       }
Index: lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java	(working copy)
@@ -136,4 +136,9 @@
     }
     return sum * coord;
   }
+
+  @Override
+  public float freq() throws IOException {
+    return scorers.length;
+  }
 }
Index: lucene/core/src/java/org/apache/lucene/util/ScorerDocQueue.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/util/ScorerDocQueue.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/util/ScorerDocQueue.java	(working copy)
@@ -29,7 +29,9 @@
   require log(size) time. The ordering is by Scorer.doc().
  *
  * @lucene.internal
+ * @deprecated 
  */
+@Deprecated
 public class ScorerDocQueue {  // later: SpansQueue for spans with doc and term positions
   private final HeapedScorerDoc[] heap;
   private final int maxSize;
