LUCENE-5487: merge trunk

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/branches/lucene5487@1576473 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java b/lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java
index e6dfa3b..23154f9 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java
@@ -242,7 +242,7 @@
       for (Iterator<Weight> wIter = weights.iterator(); wIter.hasNext();) {
         Weight w = wIter.next();
         BooleanClause c = cIter.next();
-        if (w.scorer(context, true, true, context.reader().getLiveDocs()) == null) {
+        if (w.scorer(context, context.reader().getLiveDocs()) == null) {
           if (c.isRequired()) {
             fail = true;
             Explanation r = new Explanation(0.0f, "no match on required clause (" + c.getQuery().toString() + ")");
@@ -305,8 +305,43 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs)
+    public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder,
+                                 Bits acceptDocs) throws IOException {
+
+      if (scoreDocsInOrder || minNrShouldMatch > 1) {
+        // TODO: (LUCENE-4872) in some cases BooleanScorer may be faster for minNrShouldMatch
+        // but the same is even true of pure conjunctions...
+        return super.bulkScorer(context, scoreDocsInOrder, acceptDocs);
+      }
+
+      List<BulkScorer> prohibited = new ArrayList<BulkScorer>();
+      List<BulkScorer> optional = new ArrayList<BulkScorer>();
+      Iterator<BooleanClause> cIter = clauses.iterator();
+      for (Weight w  : weights) {
+        BooleanClause c =  cIter.next();
+        BulkScorer subScorer = w.bulkScorer(context, false, acceptDocs);
+        if (subScorer == null) {
+          if (c.isRequired()) {
+            return null;
+          }
+        } else if (c.isRequired()) {
+          // TODO: there are some cases where BooleanScorer
+          // would handle conjunctions faster than
+          // BooleanScorer2...
+          return super.bulkScorer(context, scoreDocsInOrder, acceptDocs);
+        } else if (c.isProhibited()) {
+          prohibited.add(subScorer);
+        } else {
+          optional.add(subScorer);
+        }
+      }
+
+      // Check if we can and should return a BooleanScorer
+      return new BooleanScorer(this, disableCoord, minNrShouldMatch, optional, prohibited, maxCoord);
+    }
+
+    @Override
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs)
         throws IOException {
       List<Scorer> required = new ArrayList<Scorer>();
       List<Scorer> prohibited = new ArrayList<Scorer>();
@@ -314,7 +349,7 @@
       Iterator<BooleanClause> cIter = clauses.iterator();
       for (Weight w  : weights) {
         BooleanClause c =  cIter.next();
-        Scorer subScorer = w.scorer(context, true, false, acceptDocs);
+        Scorer subScorer = w.scorer(context, acceptDocs);
         if (subScorer == null) {
           if (c.isRequired()) {
             return null;
@@ -328,20 +363,6 @@
         }
       }
 
-      // NOTE: we could also use BooleanScorer, if we knew
-      // this BooleanQuery was embedded in another
-      // BooleanQuery that was also using BooleanScorer (ie,
-      // BooleanScorer can nest).  But this is hard to
-      // detect and we never do so today... (ie, we only
-      // return BooleanScorer for topScorer):
-
-      // Check if we can and should return a BooleanScorer
-      // TODO: (LUCENE-4872) in some cases BooleanScorer may be faster for minNrShouldMatch
-      // but the same is even true of pure conjunctions...
-      if (!scoreDocsInOrder && topScorer && required.size() == 0 && minNrShouldMatch <= 1) {
-        return new BooleanScorer(this, disableCoord, minNrShouldMatch, optional, prohibited, maxCoord);
-      }
-      
       if (required.size() == 0 && optional.size() == 0) {
         // no required and optional clauses.
         return null;
@@ -373,9 +394,14 @@
     
     @Override
     public boolean scoresDocsOutOfOrder() {
+      if (minNrShouldMatch > 1) {
+        // BS2 (in-order) will be used by scorer()
+        return false;
+      }
       for (BooleanClause c : clauses) {
         if (c.isRequired()) {
-          return false; // BS2 (in-order) will be used by scorer()
+          // BS2 (in-order) will be used by scorer()
+          return false;
         }
       }
       
diff --git a/lucene/core/src/java/org/apache/lucene/search/BooleanScorer.java b/lucene/core/src/java/org/apache/lucene/search/BooleanScorer.java
index c470290..7c2b6aa 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BooleanScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanScorer.java
@@ -23,6 +23,7 @@
 import java.util.List;
 
 import org.apache.lucene.index.AtomicReaderContext;
+import org.apache.lucene.index.DocsEnum;
 import org.apache.lucene.search.BooleanQuery.BooleanWeight;
 
 /* Description from Doug Cutting (excerpted from
@@ -58,7 +59,7 @@
  * conjunction can reduce the number of priority queue
  * updates for the optional terms. */
 
-final class BooleanScorer extends Scorer {
+final class BooleanScorer extends BulkScorer {
   
   private static final class BooleanScorerCollector extends Collector {
     private BucketTable bucketTable;
@@ -108,38 +109,6 @@
 
   }
   
-  // An internal class which is used in score(Collector, int) for setting the
-  // current score. This is required since Collector exposes a setScorer method
-  // and implementations that need the score will call scorer.score().
-  // Therefore the only methods that are implemented are score() and doc().
-  private static final class BucketScorer extends Scorer {
-
-    double score;
-    int doc = NO_MORE_DOCS;
-    int freq;
-    
-    public BucketScorer(Weight weight) { super(weight); }
-    
-    @Override
-    public int advance(int target) { return NO_MORE_DOCS; }
-
-    @Override
-    public int docID() { return doc; }
-
-    @Override
-    public int freq() { return freq; }
-
-    @Override
-    public int nextDoc() { return NO_MORE_DOCS; }
-    
-    @Override
-    public float score() { return (float)score; }
-    
-    @Override
-    public long cost() { return 1; }
-
-  }
-
   static final class Bucket {
     int doc = -1;            // tells if bucket is valid
     double score;             // incremental score
@@ -175,19 +144,21 @@
   }
 
   static final class SubScorer {
-    public Scorer scorer;
+    public BulkScorer scorer;
     // TODO: re-enable this if BQ ever sends us required clauses
     //public boolean required = false;
     public boolean prohibited;
     public Collector collector;
     public SubScorer next;
+    public boolean more;
 
-    public SubScorer(Scorer scorer, boolean required, boolean prohibited,
+    public SubScorer(BulkScorer scorer, boolean required, boolean prohibited,
         Collector collector, SubScorer next) {
       if (required) {
         throw new IllegalArgumentException("this scorer cannot handle required=true");
       }
       this.scorer = scorer;
+      this.more = true;
       // TODO: re-enable this if BQ ever sends us required clauses
       //this.required = required;
       this.prohibited = prohibited;
@@ -206,26 +177,20 @@
   private Bucket current;
   // Any time a prohibited clause matches we set bit 0:
   private static final int PROHIBITED_MASK = 1;
-  
-  BooleanScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
-      List<Scorer> optionalScorers, List<Scorer> prohibitedScorers, int maxCoord) throws IOException {
-    super(weight);
-    this.minNrShouldMatch = minNrShouldMatch;
 
-    if (optionalScorers != null && optionalScorers.size() > 0) {
-      for (Scorer scorer : optionalScorers) {
-        if (scorer.nextDoc() != NO_MORE_DOCS) {
-          scorers = new SubScorer(scorer, false, false, bucketTable.newCollector(0), scorers);
-        }
-      }
+  private final Weight weight;
+
+  BooleanScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
+      List<BulkScorer> optionalScorers, List<BulkScorer> prohibitedScorers, int maxCoord) throws IOException {
+    this.minNrShouldMatch = minNrShouldMatch;
+    this.weight = weight;
+
+    for (BulkScorer scorer : optionalScorers) {
+      scorers = new SubScorer(scorer, false, false, bucketTable.newCollector(0), scorers);
     }
     
-    if (prohibitedScorers != null && prohibitedScorers.size() > 0) {
-      for (Scorer scorer : prohibitedScorers) {
-        if (scorer.nextDoc() != NO_MORE_DOCS) {
-          scorers = new SubScorer(scorer, false, true, bucketTable.newCollector(PROHIBITED_MASK), scorers);
-        }
-      }
+    for (BulkScorer scorer : prohibitedScorers) {
+      scorers = new SubScorer(scorer, false, true, bucketTable.newCollector(PROHIBITED_MASK), scorers);
     }
 
     coordFactors = new float[optionalScorers.size() + 1];
@@ -234,17 +199,15 @@
     }
   }
 
-  // firstDocID is ignored since nextDoc() initializes 'current'
   @Override
-  public boolean score(Collector collector, int max, int firstDocID) throws IOException {
-    // Make sure it's only BooleanScorer that calls us:
-    assert firstDocID == -1;
+  public boolean score(Collector collector, int max) throws IOException {
+
     boolean more;
     Bucket tmp;
-    BucketScorer bs = new BucketScorer(weight);
+    FakeScorer fs = new FakeScorer();
 
     // The internal loop will set the score and doc before calling collect.
-    collector.setScorer(bs);
+    collector.setScorer(fs);
     do {
       bucketTable.first = null;
       
@@ -263,7 +226,7 @@
           // that should work)... but in theory an outside
           // app could pass a different max so we must check
           // it:
-          if (current.doc >= max){
+          if (current.doc >= max) {
             tmp = current;
             current = current.next;
             tmp.next = bucketTable.first;
@@ -272,9 +235,9 @@
           }
           
           if (current.coord >= minNrShouldMatch) {
-            bs.score = current.score * coordFactors[current.coord];
-            bs.doc = current.doc;
-            bs.freq = current.coord;
+            fs.score = (float) (current.score * coordFactors[current.coord]);
+            fs.doc = current.doc;
+            fs.freq = current.coord;
             collector.collect(current.doc);
           }
         }
@@ -292,9 +255,9 @@
       more = false;
       end += BucketTable.SIZE;
       for (SubScorer sub = scorers; sub != null; sub = sub.next) {
-        int subScorerDocID = sub.scorer.docID();
-        if (subScorerDocID != NO_MORE_DOCS) {
-          more |= sub.scorer.score(sub.collector, end, subScorerDocID);
+        if (sub.more) {
+          sub.more = sub.scorer.score(sub.collector, end);
+          more |= sub.more;
         }
       }
       current = bucketTable.first;
@@ -303,43 +266,8 @@
 
     return false;
   }
-  
-  @Override
-  public int advance(int target) {
-    throw new UnsupportedOperationException();
-  }
 
   @Override
-  public int docID() {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public int nextDoc() {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public float score() {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public int freq() throws IOException {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public long cost() {
-    return Integer.MAX_VALUE;
-  }
-
-  @Override
-  public void score(Collector collector) throws IOException {
-    score(collector, Integer.MAX_VALUE, -1);
-  }
-  
-  @Override
   public String toString() {
     StringBuilder buffer = new StringBuilder();
     buffer.append("boolean(");
@@ -350,9 +278,4 @@
     buffer.append(")");
     return buffer.toString();
   }
-  
-  @Override
-  public Collection<ChildScorer> getChildren() {
-    throw new UnsupportedOperationException();
-  }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java b/lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java
index 85fa403..ff2d870 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java
@@ -279,28 +279,6 @@
                                 : new MinShouldMatchSumScorer(weight, prohibitedScorers)));
   }
 
-  /** 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 ((doc = countingSumScorer.nextDoc()) != NO_MORE_DOCS) {
-      collector.collect(doc);
-    }
-  }
-  
-  @Override
-  public boolean score(Collector collector, int max, int firstDocID) throws IOException {
-    doc = firstDocID;
-    collector.setScorer(this);
-    while (doc < max) {
-      collector.collect(doc);
-      doc = countingSumScorer.nextDoc();
-    }
-    return doc != NO_MORE_DOCS;
-  }
-
   @Override
   public int docID() {
     return doc;
diff --git a/lucene/core/src/java/org/apache/lucene/search/BulkScorer.java b/lucene/core/src/java/org/apache/lucene/search/BulkScorer.java
new file mode 100644
index 0000000..2331cae
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/search/BulkScorer.java
@@ -0,0 +1,46 @@
+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;
+
+/** This class is used to score a range of documents at
+ *  once, and is returned by {@link Weight#bulkScorer}.  Only
+ *  queries that have a more optimized means of scoring
+ *  across a range of documents need to override this.
+ *  Otherwise, a default implementation is wrapped around
+ *  the {@link Scorer} returned by {@link Weight#scorer}. */
+
+public abstract class BulkScorer {
+
+  /** Scores and collects all matching documents.
+   * @param collector The collector to which all matching documents are passed.
+   */
+  public void score(Collector collector) throws IOException {
+    score(collector, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Collects matching documents in a range.
+   * 
+   * @param collector The collector to which all matching documents are passed.
+   * @param max Score up to, but not including, this doc
+   * @return true if more matching documents may remain.
+   */
+  public abstract boolean score(Collector collector, int max) throws IOException;
+}
diff --git a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
index ea4b809..a917a0c 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
@@ -134,8 +134,23 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, final Bits acceptDocs) throws IOException {
+    public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs) throws IOException {
+      final DocIdSetIterator disi;
+      if (filter != null) {
+        assert query == null;
+        return super.bulkScorer(context, scoreDocsInOrder, acceptDocs);
+      } else {
+        assert query != null && innerWeight != null;
+        BulkScorer bulkScorer = innerWeight.bulkScorer(context, scoreDocsInOrder, acceptDocs);
+        if (bulkScorer == null) {
+          return null;
+        }
+        return new ConstantBulkScorer(bulkScorer, this, queryWeight);
+      }
+    }
+
+    @Override
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       final DocIdSetIterator disi;
       if (filter != null) {
         assert query == null;
@@ -146,7 +161,7 @@
         disi = dis.iterator();
       } else {
         assert query != null && innerWeight != null;
-        disi = innerWeight.scorer(context, scoreDocsInOrder, topScorer, acceptDocs);
+        disi = innerWeight.scorer(context, acceptDocs);
       }
 
       if (disi == null) {
@@ -154,7 +169,7 @@
       }
       return new ConstantScorer(disi, this, queryWeight);
     }
-    
+
     @Override
     public boolean scoresDocsOutOfOrder() {
       return (innerWeight != null) ? innerWeight.scoresDocsOutOfOrder() : false;
@@ -162,7 +177,7 @@
 
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      final Scorer cs = scorer(context, true, false, context.reader().getLiveDocs());
+      final Scorer cs = scorer(context, context.reader().getLiveDocs());
       final boolean exists = (cs != null && cs.advance(doc) == doc);
 
       final ComplexExplanation result = new ComplexExplanation();
@@ -181,6 +196,52 @@
     }
   }
 
+  /** We return this as our {@link BulkScorer} so that if the CSQ
+   *  wraps a query with its own optimized top-level
+   *  scorer (e.g. BooleanScorer) we can use that
+   *  top-level scorer. */
+  protected class ConstantBulkScorer extends BulkScorer {
+    final BulkScorer bulkScorer;
+    final Weight weight;
+    final float theScore;
+
+    public ConstantBulkScorer(BulkScorer bulkScorer, Weight weight, float theScore) {
+      this.bulkScorer = bulkScorer;
+      this.weight = weight;
+      this.theScore = theScore;
+    }
+
+    @Override
+    public boolean score(Collector collector, int max) throws IOException {
+      return bulkScorer.score(wrapCollector(collector), max);
+    }
+
+    private Collector wrapCollector(final Collector collector) {
+      return new Collector() {
+        @Override
+        public void setScorer(Scorer scorer) throws IOException {
+          // we must wrap again here, but using the scorer passed in as parameter:
+          collector.setScorer(new ConstantScorer(scorer, weight, theScore));
+        }
+        
+        @Override
+        public void collect(int doc) throws IOException {
+          collector.collect(doc);
+        }
+        
+        @Override
+        public void setNextReader(AtomicReaderContext context) throws IOException {
+          collector.setNextReader(context);
+        }
+        
+        @Override
+        public boolean acceptsDocsOutOfOrder() {
+          return collector.acceptsDocsOutOfOrder();
+        }
+      };
+    }
+  }
+
   protected class ConstantScorer extends Scorer {
     final DocIdSetIterator docIdSetIterator;
     final float theScore;
@@ -222,57 +283,13 @@
       return docIdSetIterator.cost();
     }
 
-    private Collector wrapCollector(final Collector collector) {
-      return new Collector() {
-        @Override
-        public void setScorer(Scorer scorer) throws IOException {
-          // we must wrap again here, but using the scorer passed in as parameter:
-          collector.setScorer(new ConstantScorer(scorer, ConstantScorer.this.weight, ConstantScorer.this.theScore));
-        }
-        
-        @Override
-        public void collect(int doc) throws IOException {
-          collector.collect(doc);
-        }
-        
-        @Override
-        public void setNextReader(AtomicReaderContext context) throws IOException {
-          collector.setNextReader(context);
-        }
-        
-        @Override
-        public boolean acceptsDocsOutOfOrder() {
-          return collector.acceptsDocsOutOfOrder();
-        }
-      };
-    }
-
-    // this optimization allows out of order scoring as top scorer!
-    @Override
-    public void score(Collector collector) throws IOException {
-      if (query != null) {
-        ((Scorer) docIdSetIterator).score(wrapCollector(collector));
-      } else {
-        super.score(collector);
-      }
-    }
-
-    // this optimization allows out of order scoring as top scorer,
-    @Override
-    public boolean score(Collector collector, int max, int firstDocID) throws IOException {
-      if (query != null) {
-        return ((Scorer) docIdSetIterator).score(wrapCollector(collector), max, firstDocID);
-      } else {
-        return super.score(collector, max, firstDocID);
-      }
-    }
-
     @Override
     public Collection<ChildScorer> getChildren() {
-      if (query != null)
+      if (query != null) {
         return Collections.singletonList(new ChildScorer((Scorer) docIdSetIterator, "constant"));
-      else
+      } else {
         return Collections.emptyList();
+      }
     }
   }
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
index f61a54f..2e49ca1 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
@@ -153,12 +153,11 @@
 
     /** Create the scorer used to score our associated DisjunctionMaxQuery */
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       List<Scorer> scorers = new ArrayList<Scorer>();
       for (Weight w : weights) {
         // we will advance() subscorers
-        Scorer subScorer = w.scorer(context, true, false, acceptDocs);
+        Scorer subScorer = w.scorer(context, acceptDocs);
         if (subScorer != null) {
           scorers.add(subScorer);
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/FakeScorer.java b/lucene/core/src/java/org/apache/lucene/search/FakeScorer.java
new file mode 100644
index 0000000..89b92a5
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/search/FakeScorer.java
@@ -0,0 +1,72 @@
+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.util.Collection;
+
+/** Used by {@link BulkScorer}s that need to pass a {@link
+ *  Scorer} to {@link Collector#setScorer}. */
+final class FakeScorer extends Scorer {
+  float score;
+  int doc = -1;
+  int freq = 1;
+
+  public FakeScorer() {
+    super(null);
+  }
+    
+  @Override
+  public int advance(int target) {
+    throw new UnsupportedOperationException("FakeScorer doesn't support advance(int)");
+  }
+
+  @Override
+  public int docID() {
+    return doc;
+  }
+
+  @Override
+  public int freq() {
+    return freq;
+  }
+
+  @Override
+  public int nextDoc() {
+    throw new UnsupportedOperationException("FakeScorer doesn't support nextDoc()");
+  }
+    
+  @Override
+  public float score() {
+    return score;
+  }
+
+  @Override
+  public long cost() {
+    return 1;
+  }
+
+  @Override
+  public Weight getWeight() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Collection<ChildScorer> getChildren() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java b/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java
index e65561a..f4ef594 100644
--- a/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java
@@ -93,12 +93,12 @@
       }
 
       @Override
-      public void normalize (float norm, float topLevelBoost) { 
+      public void normalize(float norm, float topLevelBoost) { 
         weight.normalize(norm, topLevelBoost * getBoost()); // incorporate boost
       }
 
       @Override
-      public Explanation explain (AtomicReaderContext ir, int i) throws IOException {
+      public Explanation explain(AtomicReaderContext ir, int i) throws IOException {
         Explanation inner = weight.explain (ir, i);
         Filter f = FilteredQuery.this.filter;
         DocIdSet docIdSet = f.getDocIdSet(ir, ir.reader().getLiveDocs());
@@ -124,16 +124,30 @@
 
       // return a filtering scorer
       @Override
-      public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, final Bits acceptDocs) throws IOException {
+      public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
         assert filter != null;
 
-        final DocIdSet filterDocIdSet = filter.getDocIdSet(context, acceptDocs);
+        DocIdSet filterDocIdSet = filter.getDocIdSet(context, acceptDocs);
         if (filterDocIdSet == null) {
           // this means the filter does not accept any documents.
           return null;
         }
 
-        return strategy.filteredScorer(context, scoreDocsInOrder, topScorer, weight, filterDocIdSet);
+        return strategy.filteredScorer(context, weight, filterDocIdSet);
+      }
+
+      // return a filtering top scorer
+      @Override
+      public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs) throws IOException {
+        assert filter != null;
+
+        DocIdSet filterDocIdSet = filter.getDocIdSet(context, acceptDocs);
+        if (filterDocIdSet == null) {
+          // this means the filter does not accept any documents.
+          return null;
+        }
+
+        return strategy.filteredBulkScorer(context, weight, scoreDocsInOrder, filterDocIdSet);
       }
     };
   }
@@ -147,37 +161,20 @@
   private static final class QueryFirstScorer extends Scorer {
     private final Scorer scorer;
     private int scorerDoc = -1;
-    private Bits filterbits;
+    private final Bits filterBits;
 
     protected QueryFirstScorer(Weight weight, Bits filterBits, Scorer other) {
       super(weight);
       this.scorer = other;
-      this.filterbits = filterBits;
+      this.filterBits = filterBits;
     }
-    
-    // optimization: we are topScorer and collect directly
-    @Override
-    public void score(Collector collector) throws IOException {
-      // the normalization trick already applies the boost of this query,
-      // so we can use the wrapped scorer directly:
-      collector.setScorer(scorer);
-      for (;;) {
-        final int scorerDoc = scorer.nextDoc();
-        if (scorerDoc == DocIdSetIterator.NO_MORE_DOCS) {
-          break;
-        }
-        if (filterbits.get(scorerDoc)) {
-          collector.collect(scorerDoc);
-        }
-      }
-    }
-    
+
     @Override
     public int nextDoc() throws IOException {
       int doc;
       for(;;) {
         doc = scorer.nextDoc();
-        if (doc == Scorer.NO_MORE_DOCS || filterbits.get(doc)) {
+        if (doc == Scorer.NO_MORE_DOCS || filterBits.get(doc)) {
           return scorerDoc = doc;
         }
       } 
@@ -186,7 +183,7 @@
     @Override
     public int advance(int target) throws IOException {
       int doc = scorer.advance(target);
-      if (doc != Scorer.NO_MORE_DOCS && !filterbits.get(doc)) {
+      if (doc != Scorer.NO_MORE_DOCS && !filterBits.get(doc)) {
         return scorerDoc = nextDoc();
       } else {
         return scorerDoc = doc;
@@ -216,6 +213,40 @@
       return scorer.cost();
     }
   }
+
+  private static class QueryFirstBulkScorer extends BulkScorer {
+
+    private final Scorer scorer;
+    private final Bits filterBits;
+
+    public QueryFirstBulkScorer(Scorer scorer, Bits filterBits) {
+      this.scorer = scorer;
+      this.filterBits = filterBits;
+    }
+
+    @Override
+    public boolean score(Collector collector, int maxDoc) throws IOException {
+      // the normalization trick already applies the boost of this query,
+      // so we can use the wrapped scorer directly:
+      collector.setScorer(scorer);
+      if (scorer.docID() == -1) {
+        scorer.nextDoc();
+      }
+      while (true) {
+        final int scorerDoc = scorer.docID();
+        if (scorerDoc < maxDoc) {
+          if (filterBits.get(scorerDoc)) {
+            collector.collect(scorerDoc);
+          }
+          scorer.nextDoc();
+        } else {
+          break;
+        }
+      }
+
+      return scorer.docID() != Scorer.NO_MORE_DOCS;
+    }
+  }
   
   /**
    * A Scorer that uses a "leap-frog" approach (also called "zig-zag join"). The scorer and the filter
@@ -236,32 +267,7 @@
       this.secondary = secondary;
       this.scorer = scorer;
     }
-    
-    // optimization: we are topScorer and collect directly using short-circuited algo
-    @Override
-    public final void score(Collector collector) throws IOException {
-      // the normalization trick already applies the boost of this query,
-      // so we can use the wrapped scorer directly:
-      collector.setScorer(scorer);
-      int primDoc = primaryNext();
-      int secDoc = secondary.advance(primDoc);
-      for (;;) {
-        if (primDoc == secDoc) {
-          // Check if scorer has exhausted, only before collecting.
-          if (primDoc == DocIdSetIterator.NO_MORE_DOCS) {
-            break;
-          }
-          collector.collect(primDoc);
-          primDoc = primary.nextDoc();
-          secDoc = secondary.advance(primDoc);
-        } else if (secDoc > primDoc) {
-          primDoc = primary.advance(secDoc);
-        } else {
-          secDoc = secondary.advance(primDoc);
-        }
-      }
-    }
-    
+
     private final int advanceToNextCommonDoc() throws IOException {
       for (;;) {
         if (secondaryDoc < primaryDoc) {
@@ -472,18 +478,6 @@
      * 
      * @param context
      *          the {@link AtomicReaderContext} for which to return the {@link Scorer}.
-     * @param scoreDocsInOrder
-     *          specifies whether in-order scoring of documents is required. Note
-     *          that if set to false (i.e., out-of-order scoring is required),
-     *          this method can return whatever scoring mode it supports, as every
-     *          in-order scorer is also an out-of-order one. However, an
-     *          out-of-order scorer may not support {@link Scorer#nextDoc()}
-     *          and/or {@link Scorer#advance(int)}, therefore it is recommended to
-     *          request an in-order scorer if use of these methods is required.
-     * @param topScorer
-     *          if true, {@link Scorer#score(Collector)} will be called; if false,
-     *          {@link Scorer#nextDoc()} and/or {@link Scorer#advance(int)} will
-     *          be called.
      * @param weight the {@link FilteredQuery} {@link Weight} to create the filtered scorer.
      * @param docIdSet the filter {@link DocIdSet} to apply
      * @return a filtered scorer
@@ -491,8 +485,30 @@
      * @throws IOException if an {@link IOException} occurs
      */
     public abstract Scorer filteredScorer(AtomicReaderContext context,
-        boolean scoreDocsInOrder, boolean topScorer, Weight weight,
-        DocIdSet docIdSet) throws IOException;
+        Weight weight, DocIdSet docIdSet) throws IOException;
+
+    /**
+     * Returns a filtered {@link BulkScorer} based on this
+     * strategy.  This is an optional method: the default
+     * implementation just calls {@link #filteredScorer} and
+     * wraps that into a BulkScorer.
+     *
+     * @param context
+     *          the {@link AtomicReaderContext} for which to return the {@link Scorer}.
+     * @param weight the {@link FilteredQuery} {@link Weight} to create the filtered scorer.
+     * @param docIdSet the filter {@link DocIdSet} to apply
+     * @return a filtered top scorer
+     */
+    public BulkScorer filteredBulkScorer(AtomicReaderContext context,
+        Weight weight, boolean scoreDocsInOrder, DocIdSet docIdSet) throws IOException {
+      Scorer scorer = filteredScorer(context, weight, docIdSet);
+      if (scorer == null) {
+        return null;
+      }
+      // This impl always scores docs in order, so we can
+      // ignore scoreDocsInOrder:
+      return new Weight.DefaultBulkScorer(scorer);
+    }
   }
   
   /**
@@ -506,7 +522,7 @@
   public static class RandomAccessFilterStrategy extends FilterStrategy {
 
     @Override
-    public Scorer filteredScorer(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, Weight weight, DocIdSet docIdSet) throws IOException {
+    public Scorer filteredScorer(AtomicReaderContext context, Weight weight, DocIdSet docIdSet) throws IOException {
       final DocIdSetIterator filterIter = docIdSet.iterator();
       if (filterIter == null) {
         // this means the filter does not accept any documents.
@@ -523,12 +539,12 @@
       final boolean useRandomAccess = filterAcceptDocs != null && useRandomAccess(filterAcceptDocs, firstFilterDoc);
       if (useRandomAccess) {
         // if we are using random access, we return the inner scorer, just with other acceptDocs
-        return weight.scorer(context, scoreDocsInOrder, topScorer, filterAcceptDocs);
+        return weight.scorer(context, filterAcceptDocs);
       } else {
         assert firstFilterDoc > -1;
         // we are gonna advance() this scorer, so we set inorder=true/toplevel=false
         // we pass null as acceptDocs, as our filter has already respected acceptDocs, no need to do twice
-        final Scorer scorer = weight.scorer(context, true, false, null);
+        final Scorer scorer = weight.scorer(context, null);
         // TODO once we have way to figure out if we use RA or LeapFrog we can remove this scorer
         return (scorer == null) ? null : new PrimaryAdvancedLeapFrogScorer(weight, firstFilterDoc, filterIter, scorer);
       }
@@ -559,25 +575,27 @@
     private LeapFrogFilterStrategy(boolean scorerFirst) {
       this.scorerFirst = scorerFirst;
     }
+
     @Override
     public Scorer filteredScorer(AtomicReaderContext context,
-        boolean scoreDocsInOrder, boolean topScorer, Weight weight,
-        DocIdSet docIdSet) throws IOException {
+        Weight weight, DocIdSet docIdSet) throws IOException {
       final DocIdSetIterator filterIter = docIdSet.iterator();
       if (filterIter == null) {
         // this means the filter does not accept any documents.
         return null;
       }
-      // we are gonna advance() this scorer, so we set inorder=true/toplevel=false
       // we pass null as acceptDocs, as our filter has already respected acceptDocs, no need to do twice
-      final Scorer scorer = weight.scorer(context, true, false, null);
+      final Scorer scorer = weight.scorer(context, null);
+      if (scorer == null) {
+        return null;
+      }
+
       if (scorerFirst) {
-        return (scorer == null) ? null : new LeapFrogScorer(weight, scorer, filterIter, scorer);  
+        return new LeapFrogScorer(weight, scorer, filterIter, scorer);  
       } else {
-        return (scorer == null) ? null : new LeapFrogScorer(weight, filterIter, scorer, scorer);  
+        return new LeapFrogScorer(weight, filterIter, scorer, scorer);  
       }
     }
-    
   }
   
   /**
@@ -596,16 +614,33 @@
   private static final class QueryFirstFilterStrategy extends FilterStrategy {
     @Override
     public Scorer filteredScorer(final AtomicReaderContext context,
-        boolean scoreDocsInOrder, boolean topScorer, Weight weight,
+        Weight weight,
         DocIdSet docIdSet) throws IOException {
       Bits filterAcceptDocs = docIdSet.bits();
       if (filterAcceptDocs == null) {
-        return LEAP_FROG_QUERY_FIRST_STRATEGY.filteredScorer(context, scoreDocsInOrder, topScorer, weight, docIdSet);
+        // Filter does not provide random-access Bits; we
+        // must fallback to leapfrog:
+        return LEAP_FROG_QUERY_FIRST_STRATEGY.filteredScorer(context, weight, docIdSet);
       }
-      final Scorer scorer = weight.scorer(context, true, false, null);
+      final Scorer scorer = weight.scorer(context, null);
       return scorer == null ? null : new QueryFirstScorer(weight,
           filterAcceptDocs, scorer);
     }
+
+    @Override
+    public BulkScorer filteredBulkScorer(final AtomicReaderContext context,
+        Weight weight,
+        boolean scoreDocsInOrder, // ignored (we always top-score in order)
+        DocIdSet docIdSet) throws IOException {
+      Bits filterAcceptDocs = docIdSet.bits();
+      if (filterAcceptDocs == null) {
+        // Filter does not provide random-access Bits; we
+        // must fallback to leapfrog:
+        return LEAP_FROG_QUERY_FIRST_STRATEGY.filteredBulkScorer(context, weight, scoreDocsInOrder, docIdSet);
+      }
+      final Scorer scorer = weight.scorer(context, null);
+      return scorer == null ? null : new QueryFirstBulkScorer(scorer, filterAcceptDocs);
+    }
   }
   
 }
diff --git a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java
index 266a26e..8a9aec9 100644
--- a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java
+++ b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java
@@ -607,7 +607,7 @@
         // continue with the following leaf
         continue;
       }
-      Scorer scorer = weight.scorer(ctx, !collector.acceptsDocsOutOfOrder(), true, ctx.reader().getLiveDocs());
+      BulkScorer scorer = weight.bulkScorer(ctx, !collector.acceptsDocsOutOfOrder(), ctx.reader().getLiveDocs());
       if (scorer != null) {
         try {
           scorer.score(collector);
@@ -768,45 +768,6 @@
       this.doMaxScore = doMaxScore;
     }
 
-    private final class FakeScorer extends Scorer {
-      float score;
-      int doc;
-
-      public FakeScorer() {
-        super(null);
-      }
-    
-      @Override
-      public int advance(int target) {
-        throw new UnsupportedOperationException("FakeScorer doesn't support advance(int)");
-      }
-
-      @Override
-      public int docID() {
-        return doc;
-      }
-
-      @Override
-      public int freq() {
-        throw new UnsupportedOperationException("FakeScorer doesn't support freq()");
-      }
-
-      @Override
-      public int nextDoc() {
-        throw new UnsupportedOperationException("FakeScorer doesn't support nextDoc()");
-      }
-    
-      @Override
-      public float score() {
-        return score;
-      }
-
-      @Override
-      public long cost() {
-        return 1;
-      }
-    }
-
     private final FakeScorer fakeScorer = new FakeScorer();
 
     @Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/MatchAllDocsQuery.java b/lucene/core/src/java/org/apache/lucene/search/MatchAllDocsQuery.java
index 0737a7c..8f2edd7 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MatchAllDocsQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MatchAllDocsQuery.java
@@ -114,8 +114,7 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       return new MatchAllScorer(context.reader(), acceptDocs, this, queryWeight);
     }
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java b/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
index b48accc..93fb1b6 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
@@ -179,8 +179,7 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       assert !termArrays.isEmpty();
       final AtomicReader reader = context.reader();
       final Bits liveDocs = acceptDocs;
@@ -263,7 +262,7 @@
 
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      Scorer scorer = scorer(context, true, false, context.reader().getLiveDocs());
+      Scorer scorer = scorer(context, context.reader().getLiveDocs());
       if (scorer != null) {
         int newDoc = scorer.advance(doc);
         if (newDoc == doc) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java b/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
index d8de142..cd40a11 100644
--- a/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
@@ -245,8 +245,7 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       assert !terms.isEmpty();
       final AtomicReader reader = context.reader();
       final Bits liveDocs = acceptDocs;
@@ -305,7 +304,7 @@
 
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      Scorer scorer = scorer(context, true, false, context.reader().getLiveDocs());
+      Scorer scorer = scorer(context, context.reader().getLiveDocs());
       if (scorer != null) {
         int newDoc = scorer.advance(doc);
         if (newDoc == doc) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/QueryWrapperFilter.java b/lucene/core/src/java/org/apache/lucene/search/QueryWrapperFilter.java
index 823b47b..1d6c8ff 100644
--- a/lucene/core/src/java/org/apache/lucene/search/QueryWrapperFilter.java
+++ b/lucene/core/src/java/org/apache/lucene/search/QueryWrapperFilter.java
@@ -56,7 +56,7 @@
     return new DocIdSet() {
       @Override
       public DocIdSetIterator iterator() throws IOException {
-        return weight.scorer(privateContext, true, false, acceptDocs);
+        return weight.scorer(privateContext, acceptDocs);
       }
       @Override
       public boolean isCacheable() { return false; }
diff --git a/lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java b/lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java
index f850f53..471dc20 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java
@@ -45,11 +45,6 @@
   }
 
   @Override
-  public boolean score(Collector collector, int max, int firstDocID) throws IOException {
-    return scorer.score(collector, max, firstDocID);
-  }
-  
-  @Override
   public float score() throws IOException {
     int doc = scorer.docID();
     if (doc != curDoc) {
@@ -76,11 +71,6 @@
   }
   
   @Override
-  public void score(Collector collector) throws IOException {
-    scorer.score(collector);
-  }
-  
-  @Override
   public int advance(int target) throws IOException {
     return scorer.advance(target);
   }
diff --git a/lucene/core/src/java/org/apache/lucene/search/Scorer.java b/lucene/core/src/java/org/apache/lucene/search/Scorer.java
index 47fef12..abcbb61 100644
--- a/lucene/core/src/java/org/apache/lucene/search/Scorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/Scorer.java
@@ -54,42 +54,6 @@
     this.weight = weight;
   }
 
-  /** Scores and collects all matching documents.
-   * @param collector The collector to which all matching documents are passed.
-   */
-  public void score(Collector collector) throws IOException {
-    assert docID() == -1; // not started
-    collector.setScorer(this);
-    int doc;
-    while ((doc = nextDoc()) != NO_MORE_DOCS) {
-      collector.collect(doc);
-    }
-  }
-
-  /**
-   * Expert: Collects matching documents in a range. Hook for optimization.
-   * Note, <code>firstDocID</code> is added to ensure that {@link #nextDoc()}
-   * was called before this method.
-   * 
-   * @param collector
-   *          The collector to which all matching documents are passed.
-   * @param max
-   *          Do not score documents past this.
-   * @param firstDocID
-   *          The first document ID (ensures {@link #nextDoc()} is called before
-   *          this method.
-   * @return true if more matching documents may remain.
-   */
-  public boolean score(Collector collector, int max, int firstDocID) throws IOException {
-    assert docID() == firstDocID;
-    collector.setScorer(this);
-    int doc;
-    for (doc = firstDocID; doc < max; doc = nextDoc()) {
-      collector.collect(doc);
-    }
-    return doc != NO_MORE_DOCS;
-  }
-  
   /** Returns the score of the current document matching the query.
    * Initially invalid, until {@link #nextDoc()} or {@link #advance(int)}
    * is called the first time, or when called from within
diff --git a/lucene/core/src/java/org/apache/lucene/search/TermQuery.java b/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
index b6a1f23..5435ccd 100644
--- a/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/TermQuery.java
@@ -75,8 +75,7 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       assert termStates.topReaderContext == ReaderUtil.getTopLevelContext(context) : "The top-reader used to create Weight (" + termStates.topReaderContext + ") is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);
       final TermsEnum termsEnum = getTermsEnum(context);
       if (termsEnum == null) {
@@ -111,7 +110,7 @@
     
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      Scorer scorer = scorer(context, true, false, context.reader().getLiveDocs());
+      Scorer scorer = scorer(context, context.reader().getLiveDocs());
       if (scorer != null) {
         int newDoc = scorer.advance(doc);
         if (newDoc == doc) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/Weight.java b/lucene/core/src/java/org/apache/lucene/search/Weight.java
index 48dd209..696c7ab 100644
--- a/lucene/core/src/java/org/apache/lucene/search/Weight.java
+++ b/lucene/core/src/java/org/apache/lucene/search/Weight.java
@@ -21,6 +21,7 @@
 
 import org.apache.lucene.index.AtomicReader; // javadocs
 import org.apache.lucene.index.AtomicReaderContext;
+import org.apache.lucene.index.DocsEnum;
 import org.apache.lucene.index.IndexReaderContext; // javadocs
 import org.apache.lucene.search.similarities.Similarity;
 import org.apache.lucene.util.Bits;
@@ -35,8 +36,7 @@
  * {@link AtomicReader} dependent state should reside in the {@link Scorer}.
  * <p>
  * Since {@link Weight} creates {@link Scorer} instances for a given
- * {@link AtomicReaderContext} ({@link #scorer(AtomicReaderContext, 
- * boolean, boolean, Bits)})
+ * {@link AtomicReaderContext} ({@link #scorer(AtomicReaderContext, Bits)})
  * callers must maintain the relationship between the searcher's top-level
  * {@link IndexReaderContext} and the context used to create a {@link Scorer}. 
  * <p>
@@ -51,7 +51,7 @@
  * <li>The query normalization factor is passed to {@link #normalize(float, float)}. At
  * this point the weighting is complete.
  * <li>A <code>Scorer</code> is constructed by
- * {@link #scorer(AtomicReaderContext, boolean, boolean, Bits)}.
+ * {@link #scorer(AtomicReaderContext, Bits)}.
  * </ol>
  * 
  * @since 2.9
@@ -91,18 +91,6 @@
    * 
    * @param context
    *          the {@link AtomicReaderContext} for which to return the {@link Scorer}.
-   * @param scoreDocsInOrder
-   *          specifies whether in-order scoring of documents is required. Note
-   *          that if set to false (i.e., out-of-order scoring is required),
-   *          this method can return whatever scoring mode it supports, as every
-   *          in-order scorer is also an out-of-order one. However, an
-   *          out-of-order scorer may not support {@link Scorer#nextDoc()}
-   *          and/or {@link Scorer#advance(int)}, therefore it is recommended to
-   *          request an in-order scorer if use of these methods is required.
-   * @param topScorer
-   *          if true, {@link Scorer#score(Collector)} will be called; if false,
-   *          {@link Scorer#nextDoc()} and/or {@link Scorer#advance(int)} will
-   *          be called.
    * @param acceptDocs
    *          Bits that represent the allowable docs to match (typically deleted docs
    *          but possibly filtering other documents)
@@ -110,19 +98,89 @@
    * @return a {@link Scorer} which scores documents in/out-of order.
    * @throws IOException if there is a low-level I/O error
    */
-  public abstract Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-      boolean topScorer, Bits acceptDocs) throws IOException;
+  public abstract Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException;
+
+  /**
+   * Optional method, to return a {@link BulkScorer} to
+   * score the query and send hits to a {@link Collector}.
+   * Only queries that have a different top-level approach
+   * need to override this; the default implementation
+   * pulls a normal {@link Scorer} and iterates and
+   * collects the resulting hits.
+   *
+   * @param context
+   *          the {@link AtomicReaderContext} for which to return the {@link Scorer}.
+   * @param scoreDocsInOrder
+   *          specifies whether in-order scoring of documents is required. Note
+   *          that if set to false (i.e., out-of-order scoring is required),
+   *          this method can return whatever scoring mode it supports, as every
+   *          in-order scorer is also an out-of-order one. However, an
+   *          out-of-order scorer may not support {@link Scorer#nextDoc()}
+   *          and/or {@link Scorer#advance(int)}, therefore it is recommended to
+   *          request an in-order scorer if use of these
+   *          methods is required.
+   * @param acceptDocs
+   *          Bits that represent the allowable docs to match (typically deleted docs
+   *          but possibly filtering other documents)
+   *
+   * @return a {@link BulkScorer} which scores documents and
+   * passes them to a collector.
+   * @throws IOException if there is a low-level I/O error
+   */
+  public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs) throws IOException {
+
+    Scorer scorer = scorer(context, acceptDocs);
+    if (scorer == null) {
+      // No docs match
+      return null;
+    }
+
+    // This impl always scores docs in order, so we can
+    // ignore scoreDocsInOrder:
+    return new DefaultBulkScorer(scorer);
+  }
+
+  /** Just wraps a Scorer and performs top scoring using it. */
+  static class DefaultBulkScorer extends BulkScorer {
+    private final Scorer scorer;
+
+    public DefaultBulkScorer(Scorer scorer) {
+      assert scorer != null;
+      this.scorer = scorer;
+    }
+
+    @Override
+    public boolean score(Collector collector, int max) throws IOException {
+      // TODO: this may be sort of weird, when we are
+      // embedded in a BooleanScorer, because we are
+      // called for every chunk of 2048 documents.  But,
+      // then, scorer is a FakeScorer in that case, so any
+      // Collector doing something "interesting" in
+      // setScorer will be forced to use BS2 anyways:
+      collector.setScorer(scorer);
+      if (scorer.docID() == -1) {
+        scorer.nextDoc();
+      }
+      int doc;
+      for (doc = scorer.docID(); doc < max; doc = scorer.nextDoc()) {
+        collector.collect(doc);
+      }
+      return doc != DocsEnum.NO_MORE_DOCS;
+    }
+  }
 
   /**
    * Returns true iff this implementation scores docs only out of order. This
    * method is used in conjunction with {@link Collector}'s
    * {@link Collector#acceptsDocsOutOfOrder() acceptsDocsOutOfOrder} and
-   * {@link #scorer(AtomicReaderContext, boolean, boolean, Bits)} to
+   * {@link #bulkScorer(AtomicReaderContext, boolean, Bits)} to
    * create a matching {@link Scorer} instance for a given {@link Collector}, or
    * vice versa.
    * <p>
    * <b>NOTE:</b> the default implementation returns <code>false</code>, i.e.
    * the <code>Scorer</code> scores documents in-order.
    */
-  public boolean scoresDocsOutOfOrder() { return false; }
+  public boolean scoresDocsOutOfOrder() {
+    return false;
+  }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/search/package.html b/lucene/core/src/java/org/apache/lucene/search/package.html
index 1a9e577..51e199e 100644
--- a/lucene/core/src/java/org/apache/lucene/search/package.html
+++ b/lucene/core/src/java/org/apache/lucene/search/package.html
@@ -53,8 +53,8 @@
 <p>
 Once a Query has been created and submitted to the {@link org.apache.lucene.search.IndexSearcher IndexSearcher}, the scoring
 process begins. After some infrastructure setup, control finally passes to the {@link org.apache.lucene.search.Weight Weight}
-implementation and its {@link org.apache.lucene.search.Scorer Scorer} instances. See the <a href="#algorithm">Algorithm</a> 
-section for more notes on the process.
+implementation and its {@link org.apache.lucene.search.Scorer Scorer} or {@link org.apache.lucene.search.BulkScorer BulkScore}
+instances. See the <a href="#algorithm">Algorithm</a> section for more notes on the process.
 </p>
     <!-- FILL IN MORE HERE -->   
     <!-- TODO: this page over-links the same things too many times -->
@@ -370,6 +370,12 @@
             {@link org.apache.lucene.search.Scorer Scorer} &mdash; An abstract class containing common
             functionality for scoring. Provides both scoring and
             explanation capabilities.  This is created per-segment.</li>
+        <li>
+            {@link org.apache.lucene.search.BulkScorer BulkScorer} &mdash; An abstract class that scores
+	    a range of documents.  A default implementation simply iterates through the hits from
+	    {@link org.apache.lucene.search.Scorer Scorer}, but some queries such as
+	    {@link org.apache.lucene.search.BooleanQuery BooleanQuery} have more efficient
+	    implementations.</li>
     </ol>
     Details on each of these classes, and their children, can be found in the subsections below.
 </p>
@@ -430,13 +436,19 @@
                 that scores via a {@link org.apache.lucene.search.similarities.Similarity Similarity} will just defer to the Similarity's implementation:
                 {@link org.apache.lucene.search.similarities.Similarity.SimWeight#normalize SimWeight#normalize(float,float)}.</li>
             <li>
-                {@link org.apache.lucene.search.Weight#scorer(org.apache.lucene.index.AtomicReaderContext, boolean, boolean, org.apache.lucene.util.Bits)
-                  scorer(AtomicReaderContext context, boolean scoresDocsInOrder, boolean topScorer, Bits acceptDocs)} &mdash;
+                {@link org.apache.lucene.search.Weight#scorer(org.apache.lucene.index.AtomicReaderContext, org.apache.lucene.util.Bits)
+                  scorer(AtomicReaderContext context, Bits acceptDocs)} &mdash;
                 Construct a new {@link org.apache.lucene.search.Scorer Scorer} for this Weight. See <a href="#scorerClass">The Scorer Class</a>
                 below for help defining a Scorer. As the name implies, the Scorer is responsible for doing the actual scoring of documents 
                 given the Query.
             </li>
             <li>
+                {@link org.apache.lucene.search.Weight#bulkScorer(org.apache.lucene.index.AtomicReaderContext, boolean, org.apache.lucene.util.Bits)
+                  scorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs)} &mdash;
+                Construct a new {@link org.apache.lucene.search.BulkScorer BulkScorer} for this Weight. See <a href="#bulkScorerClass">The BulkScorer Class</a>
+                below for help defining a BulkScorer. This is an optional method, and most queries do not implement it.
+            </li>
+            <li>
                 {@link org.apache.lucene.search.Weight#explain(org.apache.lucene.index.AtomicReaderContext, int) 
                   explain(AtomicReaderContext context, int doc)} &mdash; Provide a means for explaining why a given document was
                 scored the way it was.
@@ -489,6 +501,18 @@
             </li>
         </ol>
     </p>
+<a name="bulkScorerClass"></a>
+<h4>The BulkScorer Class</h4>
+    <p>The
+        {@link org.apache.lucene.search.BulkScorer BulkScorer} scores a range of documents.  There is only one 
+        abstract method:
+        <ol>
+            <li>
+                {@link org.apache.lucene.search.BulkScorer#score(org.apache.lucene.search.Collector,int) score(Collector,int)} &mdash;
+		Score all documents up to but not including the specified max document.
+	    </li>
+        </ol>
+    </p>
 <h4>Why would I want to add my own Query?</h4>
 
     <p>In a nutshell, you want to add your own custom Query implementation when you think that Lucene's
@@ -539,7 +563,7 @@
 <p>If a Filter is being used, some initial setup is done to determine which docs to include. 
    Otherwise, we ask the Weight for a {@link org.apache.lucene.search.Scorer Scorer} for each
    {@link org.apache.lucene.index.IndexReader IndexReader} segment and proceed by calling
-   {@link org.apache.lucene.search.Scorer#score(org.apache.lucene.search.Collector) Scorer.score()}.
+   {@link org.apache.lucene.search.BulkScorer#score(org.apache.lucene.search.Collector) BulkScorer.score(Collector)}.
 </p>
 <p>At last, we are actually going to score some documents. The score method takes in the Collector
    (most likely the TopScoreDocCollector or TopFieldCollector) and does its business.Of course, here 
diff --git a/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadNearQuery.java b/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadNearQuery.java
index 31034ea..d2e924e 100644
--- a/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadNearQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadNearQuery.java
@@ -148,15 +148,14 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       return new PayloadNearSpanScorer(query.getSpans(context, acceptDocs, termContexts), this,
           similarity, similarity.simScorer(stats, context));
     }
     
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      PayloadNearSpanScorer scorer = (PayloadNearSpanScorer) scorer(context, true, false, context.reader().getLiveDocs());
+      PayloadNearSpanScorer scorer = (PayloadNearSpanScorer) scorer(context, context.reader().getLiveDocs());
       if (scorer != null) {
         int newDoc = scorer.advance(doc);
         if (newDoc == doc) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadTermQuery.java b/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadTermQuery.java
index b263999..04ecd80 100644
--- a/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadTermQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/payloads/PayloadTermQuery.java
@@ -79,8 +79,7 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       return new PayloadTermSpanScorer((TermSpans) query.getSpans(context, acceptDocs, termContexts),
           this, similarity.simScorer(stats, context));
     }
@@ -177,7 +176,7 @@
     
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      PayloadTermSpanScorer scorer = (PayloadTermSpanScorer) scorer(context, true, false, context.reader().getLiveDocs());
+      PayloadTermSpanScorer scorer = (PayloadTermSpanScorer) scorer(context, context.reader().getLiveDocs());
       if (scorer != null) {
         int newDoc = scorer.advance(doc);
         if (newDoc == doc) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanWeight.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanWeight.java
index fb73721..9acff49 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanWeight.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanWeight.java
@@ -81,8 +81,7 @@
   }
 
   @Override
-  public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-      boolean topScorer, Bits acceptDocs) throws IOException {
+  public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
     if (stats == null) {
       return null;
     } else {
@@ -92,7 +91,7 @@
 
   @Override
   public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-    SpanScorer scorer = (SpanScorer) scorer(context, true, false, context.reader().getLiveDocs());
+    SpanScorer scorer = (SpanScorer) scorer(context, context.reader().getLiveDocs());
     if (scorer != null) {
       int newDoc = scorer.advance(doc);
       if (newDoc == doc) {
diff --git a/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java b/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java
index f34733a..ee47759 100644
--- a/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java
+++ b/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java
@@ -17,8 +17,6 @@
  * limitations under the License.
  */
 
-import java.io.IOException;
-
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
@@ -232,11 +230,6 @@
     }
 
     @Override
-    public boolean score(Collector collector, int max, int firstDocID) {
-      throw new UnsupportedOperationException(UNSUPPORTED_MSG);
-    }
-    
-    @Override
     public float score() {
       throw new UnsupportedOperationException(UNSUPPORTED_MSG);
     }
@@ -351,8 +344,7 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) {
       throw new UnsupportedOperationException(UNSUPPORTED_MSG);
     }
     
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBooleanOr.java b/lucene/core/src/test/org/apache/lucene/search/TestBooleanOr.java
index a9fe1da..b1ba0f1 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestBooleanOr.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBooleanOr.java
@@ -183,7 +183,7 @@
     Weight w = s.createNormalizedWeight(bq);
 
     assertEquals(1, s.getIndexReader().leaves().size());
-    Scorer scorer = w.scorer(s.getIndexReader().leaves().get(0), false, true, null);
+    BulkScorer scorer = w.bulkScorer(s.getIndexReader().leaves().get(0), false, null);
 
     final FixedBitSet hits = new FixedBitSet(docCount);
     final AtomicInteger end = new AtomicInteger();
@@ -211,7 +211,7 @@
     while (end.intValue() < docCount) {
       final int inc = TestUtil.nextInt(random(), 1, 1000);
       end.getAndAdd(inc);
-      scorer.score(c, end.intValue(), -1);
+      scorer.score(c, end.intValue());
     }
 
     assertEquals(docCount, hits.cardinality());
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java b/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java
index faa779a..4442db3 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java
@@ -17,6 +17,7 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -29,6 +30,7 @@
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
@@ -43,7 +45,6 @@
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.NamedThreadFactory;
 import org.apache.lucene.util.TestUtil;
-import org.apache.lucene.util.TestUtil;
 
 public class TestBooleanQuery extends LuceneTestCase {
   
@@ -234,8 +235,7 @@
 
       Weight weight = s.createNormalizedWeight(q);
 
-      Scorer scorer = weight.scorer(s.leafContexts.get(0),
-                                          true, false, null);
+      Scorer scorer = weight.scorer(s.leafContexts.get(0), null);
 
       // First pass: just use .nextDoc() to gather all hits
       final List<ScoreDoc> hits = new ArrayList<ScoreDoc>();
@@ -252,8 +252,7 @@
       for(int iter2=0;iter2<10;iter2++) {
 
         weight = s.createNormalizedWeight(q);
-        scorer = weight.scorer(s.leafContexts.get(0),
-                               true, false, null);
+        scorer = weight.scorer(s.leafContexts.get(0), null);
 
         if (VERBOSE) {
           System.out.println("  iter2=" + iter2);
@@ -327,4 +326,30 @@
     directory.close();
   }
 
+  // LUCENE-5487
+  public void testInOrderWithMinShouldMatch() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newTextField("field", "some text here", Field.Store.NO));
+    w.addDocument(doc);
+    IndexReader r = w.getReader();
+    w.close();
+    IndexSearcher s = new IndexSearcher(r) {
+        @Override
+        protected void search(List<AtomicReaderContext> leaves, Weight weight, Collector collector) throws IOException {
+          assertEquals(-1, collector.getClass().getSimpleName().indexOf("OutOfOrder"));
+          super.search(leaves, weight, collector);
+        }
+      };
+    BooleanQuery bq = new BooleanQuery();
+    bq.add(new TermQuery(new Term("field", "some")), BooleanClause.Occur.SHOULD);
+    bq.add(new TermQuery(new Term("field", "text")), BooleanClause.Occur.SHOULD);
+    bq.add(new TermQuery(new Term("field", "here")), BooleanClause.Occur.SHOULD);
+    bq.setMinimumNumberShouldMatch(2);
+    s.search(bq, 10);
+    r.close();
+    dir.close();
+  }
+
 }
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBooleanScorer.java b/lucene/core/src/test/org/apache/lucene/search/TestBooleanScorer.java
index ffeac52..41e5f60 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestBooleanScorer.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBooleanScorer.java
@@ -17,8 +17,10 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.lucene.document.Document;
@@ -30,10 +32,10 @@
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanQuery.BooleanWeight;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.LuceneTestCase;
 
-public class TestBooleanScorer extends LuceneTestCase
-{
+public class TestBooleanScorer extends LuceneTestCase {
   private static final String FIELD = "category";
   
   public void testMethod() throws Exception {
@@ -78,27 +80,23 @@
     writer.close();
     IndexSearcher searcher = newSearcher(ir);
     BooleanWeight weight = (BooleanWeight) new BooleanQuery().createWeight(searcher);
-    Scorer[] scorers = new Scorer[] {new Scorer(weight) {
+    BulkScorer[] scorers = new BulkScorer[] {new BulkScorer() {
       private int doc = -1;
-      @Override public float score() { return 0; }
-      @Override public int freq()  { return 0; }
-      @Override public int docID() { return doc; }
-      
-      @Override public int nextDoc() {
-        return doc = doc == -1 ? 3000 : NO_MORE_DOCS;
-      }
 
-      @Override public int advance(int target) {
-        return doc = target <= 3000 ? 3000 : NO_MORE_DOCS;
-      }
-      
       @Override
-      public long cost() {
-        return 1;
+      public boolean score(Collector c, int maxDoc) throws IOException {
+        assert doc == -1;
+        doc = 3000;
+        FakeScorer fs = new FakeScorer();
+        fs.doc = doc;
+        fs.score = 1.0f;
+        c.setScorer(fs);
+        c.collect(3000);
+        return false;
       }
     }};
     
-    BooleanScorer bs = new BooleanScorer(weight, false, 1, Arrays.asList(scorers), null, scorers.length);
+    BooleanScorer bs = new BooleanScorer(weight, false, 1, Arrays.asList(scorers), Collections.<BulkScorer>emptyList(), scorers.length);
 
     final List<Integer> hits = new ArrayList<Integer>();
     bs.score(new Collector() {
@@ -157,7 +155,7 @@
       public void setScorer(Scorer scorer) {
         // Make sure we got BooleanScorer:
         final Class<?> clazz = scorer instanceof AssertingScorer ? ((AssertingScorer) scorer).getIn().getClass() : scorer.getClass();
-        assertEquals("Scorer is implemented by wrong class", BooleanScorer.class.getName() + "$BucketScorer", clazz.getName());
+        assertEquals("Scorer is implemented by wrong class", FakeScorer.class.getName(), clazz.getName());
       }
       
       @Override
@@ -180,4 +178,80 @@
     r.close();
     d.close();
   }
+
+  /** Throws UOE if Weight.scorer is called */
+  private static class CrazyMustUseBulkScorerQuery extends Query {
+
+    @Override
+    public String toString(String field) {
+      return "MustUseBulkScorerQuery";
+    }
+
+    @Override
+    public Weight createWeight(IndexSearcher searcher) throws IOException {
+      return new Weight() {
+        @Override
+        public Explanation explain(AtomicReaderContext context, int doc) {
+          throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Query getQuery() {
+          return CrazyMustUseBulkScorerQuery.this;
+        }
+
+        @Override
+        public float getValueForNormalization() {
+          return 1.0f;
+        }
+
+        @Override
+        public void normalize(float norm, float topLevelBoost) {
+        }
+
+        @Override
+        public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) {
+          throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs) {
+          return new BulkScorer() {
+
+            @Override
+            public boolean score(Collector collector, int max) throws IOException {
+              collector.setScorer(new FakeScorer());
+              collector.collect(0);
+              return false;
+            }
+          };
+        }
+      };
+    }
+  }
+
+  /** Make sure BooleanScorer can embed another
+   *  BooleanScorer. */
+  public void testEmbeddedBooleanScorer() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir);
+    Document doc = new Document();
+    doc.add(newTextField("field", "doctors are people who prescribe medicines of which they know little, to cure diseases of which they know less, in human beings of whom they know nothing", Field.Store.NO));
+    w.addDocument(doc);
+    IndexReader r = w.getReader();
+    w.close();
+
+    IndexSearcher s = newSearcher(r);
+    BooleanQuery q1 = new BooleanQuery();
+    q1.add(new TermQuery(new Term("field", "little")), BooleanClause.Occur.SHOULD);
+    q1.add(new TermQuery(new Term("field", "diseases")), BooleanClause.Occur.SHOULD);
+
+    BooleanQuery q2 = new BooleanQuery();
+    q2.add(q1, BooleanClause.Occur.SHOULD);
+    q2.add(new CrazyMustUseBulkScorerQuery(), BooleanClause.Occur.SHOULD);
+
+    assertEquals(1, s.search(q2, 10).totalHits);
+    r.close();
+    dir.close();
+  }
 }
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java b/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java
index 1fa8526..21a6a7d 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java
@@ -121,7 +121,7 @@
       checkHits(searcher, csq2, csq2.getBoost(), ConstantScoreQuery.ConstantScorer.class.getName(), ConstantScoreQuery.ConstantScorer.class.getName());
       
       // for the combined BQ, the scorer should always be BooleanScorer's BucketScorer, because our scorer supports out-of order collection!
-      final String bucketScorerClass = BooleanScorer.class.getName() + "$BucketScorer";
+      final String bucketScorerClass = FakeScorer.class.getName();
       checkHits(searcher, bq, csq1.getBoost() + csq2.getBoost(), bucketScorerClass, null);
       checkHits(searcher, csqbq, csqbq.getBoost(), ConstantScoreQuery.ConstantScorer.class.getName(), bucketScorerClass);
     } finally {
@@ -158,7 +158,7 @@
   }
 
   // LUCENE-5307
-  // don't reuse the scorer of filters since they have been created with topScorer=false
+  // don't reuse the scorer of filters since they have been created with bulkScorer=false
   public void testQueryWrapperFilter() throws IOException {
     Directory d = newDirectory();
     RandomIndexWriter w = new RandomIndexWriter(random(), d);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestDisjunctionMaxQuery.java b/lucene/core/src/test/org/apache/lucene/search/TestDisjunctionMaxQuery.java
index dcaf023..4615d39 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestDisjunctionMaxQuery.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestDisjunctionMaxQuery.java
@@ -180,7 +180,7 @@
     assertTrue(s.getTopReaderContext() instanceof AtomicReaderContext);
     final Weight dw = s.createNormalizedWeight(dq);
     AtomicReaderContext context = (AtomicReaderContext)s.getTopReaderContext();
-    final Scorer ds = dw.scorer(context, true, false, context.reader().getLiveDocs());
+    final Scorer ds = dw.scorer(context, context.reader().getLiveDocs());
     final boolean skipOk = ds.advance(3) != DocIdSetIterator.NO_MORE_DOCS;
     if (skipOk) {
       fail("firsttime skipTo found a match? ... "
@@ -196,7 +196,7 @@
     QueryUtils.check(random(), dq, s);
     final Weight dw = s.createNormalizedWeight(dq);
     AtomicReaderContext context = (AtomicReaderContext)s.getTopReaderContext();
-    final Scorer ds = dw.scorer(context, true, false, context.reader().getLiveDocs());
+    final Scorer ds = dw.scorer(context, context.reader().getLiveDocs());
     assertTrue("firsttime skipTo found no match",
         ds.advance(3) != DocIdSetIterator.NO_MORE_DOCS);
     assertEquals("found wrong docid", "d4", r.document(ds.docID()).get("id"));
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestFilteredQuery.java b/lucene/core/src/test/org/apache/lucene/search/TestFilteredQuery.java
index d1ec6f5..de87f96 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestFilteredQuery.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestFilteredQuery.java
@@ -385,10 +385,10 @@
   
   private static FilteredQuery.FilterStrategy randomFilterStrategy(Random random, final boolean useRandomAccess) {
     if (useRandomAccess) {
-      return  new FilteredQuery.RandomAccessFilterStrategy() {
+      return new FilteredQuery.RandomAccessFilterStrategy() {
         @Override
         protected boolean useRandomAccess(Bits bits, int firstFilterDoc) {
-          return useRandomAccess;
+          return true;
         }
       };
     }
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestMinShouldMatch2.java b/lucene/core/src/test/org/apache/lucene/search/TestMinShouldMatch2.java
index 4ca3722..694484c 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestMinShouldMatch2.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestMinShouldMatch2.java
@@ -126,7 +126,7 @@
     if (slow) {
       return new SlowMinShouldMatchScorer(weight, reader, searcher);
     } else {
-      return weight.scorer(reader.getContext(), true, false, null);
+      return weight.scorer(reader.getContext(), null);
     }
   }
   
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestTermScorer.java b/lucene/core/src/test/org/apache/lucene/search/TestTermScorer.java
index 0c4e229..b0ff346 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestTermScorer.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestTermScorer.java
@@ -78,7 +78,7 @@
     Weight weight = indexSearcher.createNormalizedWeight(termQuery);
     assertTrue(indexSearcher.getTopReaderContext() instanceof AtomicReaderContext);
     AtomicReaderContext context = (AtomicReaderContext)indexSearcher.getTopReaderContext();
-    Scorer ts = weight.scorer(context, true, true, context.reader().getLiveDocs());
+    BulkScorer ts = weight.bulkScorer(context, true, context.reader().getLiveDocs());
     // we have 2 documents with the term all in them, one document for all the
     // other values
     final List<TestHit> docs = new ArrayList<TestHit>();
@@ -140,7 +140,7 @@
     Weight weight = indexSearcher.createNormalizedWeight(termQuery);
     assertTrue(indexSearcher.getTopReaderContext() instanceof AtomicReaderContext);
     AtomicReaderContext context = (AtomicReaderContext) indexSearcher.getTopReaderContext();
-    Scorer ts = weight.scorer(context, true, false, context.reader().getLiveDocs());
+    Scorer ts = weight.scorer(context, context.reader().getLiveDocs());
     assertTrue("next did not return a doc",
         ts.nextDoc() != DocIdSetIterator.NO_MORE_DOCS);
     assertTrue("score is not correct", ts.score() == 1.6931472f);
@@ -159,7 +159,7 @@
     Weight weight = indexSearcher.createNormalizedWeight(termQuery);
     assertTrue(indexSearcher.getTopReaderContext() instanceof AtomicReaderContext);
     AtomicReaderContext context = (AtomicReaderContext) indexSearcher.getTopReaderContext();
-    Scorer ts = weight.scorer(context, true, false, context.reader().getLiveDocs());
+    Scorer ts = weight.scorer(context, context.reader().getLiveDocs());
     assertTrue("Didn't skip", ts.advance(3) != DocIdSetIterator.NO_MORE_DOCS);
     // The next doc should be doc 5
     assertTrue("doc should be number 5", ts.docID() == 5);
diff --git a/lucene/core/src/test/org/apache/lucene/search/spans/TestNearSpansOrdered.java b/lucene/core/src/test/org/apache/lucene/search/spans/TestNearSpansOrdered.java
index 48a4b4f..e6a6cb6 100644
--- a/lucene/core/src/test/org/apache/lucene/search/spans/TestNearSpansOrdered.java
+++ b/lucene/core/src/test/org/apache/lucene/search/spans/TestNearSpansOrdered.java
@@ -167,7 +167,7 @@
     Weight w = searcher.createNormalizedWeight(q);
     IndexReaderContext topReaderContext = searcher.getTopReaderContext();
     AtomicReaderContext leave = topReaderContext.leaves().get(0);
-    Scorer s = w.scorer(leave, true, false, leave.reader().getLiveDocs());
+    Scorer s = w.scorer(leave, leave.reader().getLiveDocs());
     assertEquals(1, s.advance(1));
   }
   
diff --git a/lucene/core/src/test/org/apache/lucene/search/spans/TestSpans.java b/lucene/core/src/test/org/apache/lucene/search/spans/TestSpans.java
index 30e8a9e..1f57342 100644
--- a/lucene/core/src/test/org/apache/lucene/search/spans/TestSpans.java
+++ b/lucene/core/src/test/org/apache/lucene/search/spans/TestSpans.java
@@ -429,7 +429,7 @@
                                 slop,
                                 ordered);
   
-        spanScorer = searcher.createNormalizedWeight(snq).scorer(ctx, true, false, ctx.reader().getLiveDocs());
+        spanScorer = searcher.createNormalizedWeight(snq).scorer(ctx, ctx.reader().getLiveDocs());
       } finally {
         searcher.setSimilarity(oldSim);
       }
diff --git a/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java b/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java
index 1c76622..7057e7b 100644
--- a/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java
+++ b/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java
@@ -29,6 +29,7 @@
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.BulkScorer;
 import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.Bits;
 
@@ -117,12 +118,17 @@
       }
 
       @Override
-      public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-                           boolean topScorer, Bits acceptDocs) throws IOException {
+      public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
+        // We can only run as a top scorer:
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs) throws IOException {
 
         // TODO: it could be better if we take acceptDocs
         // into account instead of baseScorer?
-        Scorer baseScorer = baseWeight.scorer(context, scoreDocsInOrder, false, acceptDocs);
+        Scorer baseScorer = baseWeight.scorer(context, acceptDocs);
 
         DrillSidewaysScorer.DocsAndCost[] dims = new DrillSidewaysScorer.DocsAndCost[drillDowns.length];
         int nullCount = 0;
@@ -167,7 +173,7 @@
               dims[dim].disi = disi;
             }
           } else {
-            DocIdSetIterator disi = ((Weight) drillDowns[dim]).scorer(context, true, false, null);
+            DocIdSetIterator disi = ((Weight) drillDowns[dim]).scorer(context, null);
             if (disi == null) {
               nullCount++;
               continue;
diff --git a/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java b/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java
index 220e649..8764f7e 100644
--- a/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java
+++ b/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java
@@ -22,14 +22,16 @@
 import java.util.Collections;
 
 import org.apache.lucene.index.AtomicReaderContext;
+import org.apache.lucene.index.DocsEnum;
 import org.apache.lucene.search.Collector;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.BulkScorer;
 import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.FixedBitSet;
 
-class DrillSidewaysScorer extends Scorer {
+class DrillSidewaysScorer extends BulkScorer {
 
   //private static boolean DEBUG = false;
 
@@ -52,7 +54,6 @@
 
   DrillSidewaysScorer(Weight w, AtomicReaderContext context, Scorer baseScorer, Collector drillDownCollector,
                       DocsAndCost[] dims, boolean scoreSubDocsAtOnce) {
-    super(w);
     this.dims = dims;
     this.context = context;
     this.baseScorer = baseScorer;
@@ -61,18 +62,22 @@
   }
 
   @Override
-  public void score(Collector collector) throws IOException {
+  public boolean score(Collector collector, int maxDoc) throws IOException {
+    if (maxDoc != Integer.MAX_VALUE) {
+      throw new IllegalArgumentException("maxDoc must be Integer.MAX_VALUE");
+    }
     //if (DEBUG) {
     //  System.out.println("\nscore: reader=" + context.reader());
     //}
     //System.out.println("score r=" + context.reader());
-    collector.setScorer(this);
+    FakeScorer scorer = new FakeScorer();
+    collector.setScorer(scorer);
     if (drillDownCollector != null) {
-      drillDownCollector.setScorer(this);
+      drillDownCollector.setScorer(scorer);
       drillDownCollector.setNextReader(context);
     }
     for (DocsAndCost dim : dims) {
-      dim.sidewaysCollector.setScorer(this);
+      dim.sidewaysCollector.setScorer(scorer);
       dim.sidewaysCollector.setNextReader(context);
     }
 
@@ -140,6 +145,8 @@
       //System.out.println("union");
       doUnionScoring(collector, disis, sidewaysCollectors);
     }
+
+    return false;
   }
 
   /** Used when base query is highly constraining vs the
@@ -154,7 +161,7 @@
     //}
     int docID = baseScorer.docID();
 
-    nextDoc: while (docID != NO_MORE_DOCS) {
+    nextDoc: while (docID != DocsEnum.NO_MORE_DOCS) {
       Collector failedCollector = null;
       for (int i=0;i<disis.length;i++) {
         // TODO: should we sort this 2nd dimension of
@@ -612,39 +619,53 @@
     sidewaysCollector.collect(collectDocID);
   }
 
-  @Override
-  public int docID() {
-    return collectDocID;
-  }
+  private final class FakeScorer extends Scorer {
+    float score;
+    int doc;
 
-  @Override
-  public float score() {
-    return collectScore;
-  }
+    public FakeScorer() {
+      super(null);
+    }
+    
+    @Override
+    public int advance(int target) {
+      throw new UnsupportedOperationException("FakeScorer doesn't support advance(int)");
+    }
 
-  @Override
-  public int freq() {
-    return 1+dims.length;
-  }
+    @Override
+    public int docID() {
+      return collectDocID;
+    }
 
-  @Override
-  public int nextDoc() {
-    throw new UnsupportedOperationException();
-  }
+    @Override
+    public int freq() {
+      return 1+dims.length;
+    }
 
-  @Override
-  public int advance(int target) {
-    throw new UnsupportedOperationException();
-  }
+    @Override
+    public int nextDoc() {
+      throw new UnsupportedOperationException("FakeScorer doesn't support nextDoc()");
+    }
+    
+    @Override
+    public float score() {
+      return collectScore;
+    }
 
-  @Override
-  public long cost() {
-    return baseScorer.cost();
-  }
+    @Override
+    public long cost() {
+      return baseScorer.cost();
+    }
 
-  @Override
-  public Collection<ChildScorer> getChildren() {
-    return Collections.singletonList(new ChildScorer(baseScorer, "MUST"));
+    @Override
+    public Collection<ChildScorer> getChildren() {
+      return Collections.singletonList(new ChildScorer(baseScorer, "MUST"));
+    }
+
+    @Override
+    public Weight getWeight() {
+      throw new UnsupportedOperationException();
+    }
   }
 
   static class DocsAndCost implements Comparable<DocsAndCost> {
diff --git a/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyFacetSumValueSource.java b/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyFacetSumValueSource.java
index bb04db1..1342c18 100644
--- a/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyFacetSumValueSource.java
+++ b/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyFacetSumValueSource.java
@@ -18,12 +18,13 @@
  */
 
 import java.io.IOException;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.lucene.facet.FacetsCollector;
 import org.apache.lucene.facet.FacetsCollector.MatchingDocs;
+import org.apache.lucene.facet.FacetsCollector;
 import org.apache.lucene.facet.FacetsConfig;
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.queries.function.FunctionValues;
@@ -31,6 +32,7 @@
 import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.IntsRef;
 
 /** Aggregates sum of values from {@link
@@ -70,6 +72,16 @@
     @Override public int nextDoc() throws IOException { throw new UnsupportedOperationException(); }
     @Override public int advance(int target) throws IOException { throw new UnsupportedOperationException(); }
     @Override public long cost() { return 0; }
+
+    @Override
+    public Weight getWeight() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<ChildScorer> getChildren() {
+      throw new UnsupportedOperationException();
+    }
   }
 
   private final void sumValues(List<MatchingDocs> matchingDocs, boolean keepScores, ValueSource valueSource) throws IOException {
diff --git a/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java b/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java
index a39678d..195ec23 100644
--- a/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java
+++ b/lucene/grouping/src/java/org/apache/lucene/search/grouping/BlockGroupingCollector.java
@@ -18,14 +18,15 @@
  */
 
 
+import java.io.IOException;
+import java.util.Collection;
+
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.search.*;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.PriorityQueue;
 
-import java.io.IOException;
-
 // TODO: this sentence is too long for the class summary.
 /** BlockGroupingCollector performs grouping with a
  *  single pass collector, as long as you are grouping by a
@@ -90,7 +91,7 @@
     int doc;
 
     public FakeScorer() {
-      super((Weight) null);
+      super(null);
     }
 
     @Override
@@ -122,6 +123,16 @@
     public long cost() {
       return 1;
     }
+
+    @Override
+    public Weight getWeight() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<ChildScorer> getChildren() {
+      throw new UnsupportedOperationException();
+    }
   }
 
   private static final class OneGroup {
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/FakeScorer.java b/lucene/join/src/java/org/apache/lucene/search/join/FakeScorer.java
new file mode 100644
index 0000000..d4b02dc
--- /dev/null
+++ b/lucene/join/src/java/org/apache/lucene/search/join/FakeScorer.java
@@ -0,0 +1,75 @@
+package org.apache.lucene.search.join;
+
+/*
+ * 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.util.Collection;
+
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Weight;
+
+/** Passed to {@link Collector#setScorer} during join collection. */
+final class FakeScorer extends Scorer {
+  float score;
+  int doc = -1;
+  int freq = 1;
+
+  public FakeScorer() {
+    super(null);
+  }
+    
+  @Override
+  public int advance(int target) {
+    throw new UnsupportedOperationException("FakeScorer doesn't support advance(int)");
+  }
+
+  @Override
+  public int docID() {
+    return doc;
+  }
+
+  @Override
+  public int freq() {
+    throw new UnsupportedOperationException("FakeScorer doesn't support freq()");
+  }
+
+  @Override
+  public int nextDoc() {
+    throw new UnsupportedOperationException("FakeScorer doesn't support nextDoc()");
+  }
+    
+  @Override
+  public float score() {
+    return score;
+  }
+
+  @Override
+  public long cost() {
+    return 1;
+  }
+
+  @Override
+  public Weight getWeight() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Collection<ChildScorer> getChildren() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
index fac8ed7..739ef35 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/TermsIncludingScoreQuery.java
@@ -17,6 +17,10 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Set;
+
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.DocsEnum;
 import org.apache.lucene.index.IndexReader;
@@ -30,16 +34,13 @@
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.BulkScorer;
 import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefHash;
 import org.apache.lucene.util.FixedBitSet;
 
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Set;
-
 class TermsIncludingScoreQuery extends Query {
 
   final String field;
@@ -131,11 +132,9 @@
 
       @Override
       public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-        SVInnerScorer scorer = (SVInnerScorer) scorer(context, false, false, context.reader().getLiveDocs());
+        SVInnerScorer scorer = (SVInnerScorer) bulkScorer(context, false, null);
         if (scorer != null) {
-          if (scorer.advanceForExplainOnly(doc) == doc) {
-            return scorer.explain();
-          }
+          return scorer.explain(doc);
         }
         return new ComplexExplanation(false, 0.0f, "Not a match");
       }
@@ -163,7 +162,7 @@
       }
 
       @Override
-      public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, Bits acceptDocs) throws IOException {
+      public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
         Terms terms = context.reader().terms(field);
         if (terms == null) {
           return null;
@@ -173,23 +172,41 @@
         final long cost = context.reader().maxDoc() * terms.size();
 
         segmentTermsEnum = terms.iterator(segmentTermsEnum);
-        if (scoreDocsInOrder) {
-          if (multipleValuesPerDocument) {
-            return new MVInOrderScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc(), cost);
-          } else {
-            return new SVInOrderScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc(), cost);
-          }
-        } else if (multipleValuesPerDocument) {
-          return new MVInnerScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc(), cost);
+        if (multipleValuesPerDocument) {
+          return new MVInOrderScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc(), cost);
         } else {
-          return new SVInnerScorer(this, acceptDocs, segmentTermsEnum, cost);
+          return new SVInOrderScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc(), cost);
+        }
+      }
+
+      @Override
+      public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs) throws IOException {
+
+        if (scoreDocsInOrder) {
+          return super.bulkScorer(context, scoreDocsInOrder, acceptDocs);
+        } else {
+          Terms terms = context.reader().terms(field);
+          if (terms == null) {
+            return null;
+          }
+          // what is the runtime...seems ok?
+          final long cost = context.reader().maxDoc() * terms.size();
+
+          segmentTermsEnum = terms.iterator(segmentTermsEnum);
+          // Optimized impls that take advantage of docs
+          // being allowed to be out of order:
+          if (multipleValuesPerDocument) {
+            return new MVInnerScorer(this, acceptDocs, segmentTermsEnum, context.reader().maxDoc(), cost);
+          } else {
+            return new SVInnerScorer(this, acceptDocs, segmentTermsEnum, cost);
+          }
         }
       }
     };
   }
 
   // This impl assumes that the 'join' values are used uniquely per doc per field. Used for one to many relations.
-  class SVInnerScorer extends Scorer {
+  class SVInnerScorer extends BulkScorer {
 
     final BytesRef spare = new BytesRef();
     final Bits acceptDocs;
@@ -203,7 +220,6 @@
     int doc;
 
     SVInnerScorer(Weight weight, Bits acceptDocs, TermsEnum termsEnum, long cost) {
-      super(weight);
       this.acceptDocs = acceptDocs;
       this.termsEnum = termsEnum;
       this.cost = cost;
@@ -211,25 +227,20 @@
     }
 
     @Override
-    public void score(Collector collector) throws IOException {
-      collector.setScorer(this);
-      for (int doc = nextDocOutOfOrder(); doc != NO_MORE_DOCS; doc = nextDocOutOfOrder()) {
-        collector.collect(doc);
+    public boolean score(Collector collector, int max) throws IOException {
+      FakeScorer fakeScorer = new FakeScorer();
+      collector.setScorer(fakeScorer);
+      if (doc == -1) {
+        doc = nextDocOutOfOrder();
       }
-    }
+      while(doc < max) {
+        fakeScorer.doc = doc;
+        fakeScorer.score = scores[ords[scoreUpto]];
+        collector.collect(doc);
+        doc = nextDocOutOfOrder();
+      }
 
-    @Override
-    public float score() throws IOException {
-      return scores[ords[scoreUpto]];
-    }
-
-    Explanation explain() throws IOException {
-      return new ComplexExplanation(true, score(), "Score based on join value " + termsEnum.term().utf8ToString());
-    }
-
-    @Override
-    public int docID() {
-      return doc;
+      return doc != DocsEnum.NO_MORE_DOCS;
     }
 
     int nextDocOutOfOrder() throws IOException {
@@ -258,17 +269,7 @@
       return docsEnum.nextDoc();
     }
 
-    @Override
-    public int nextDoc() throws IOException {
-      throw new UnsupportedOperationException("nextDoc() isn't supported because doc ids are emitted out of order");
-    }
-
-    @Override
-    public int advance(int target) throws IOException {
-      throw new UnsupportedOperationException("advance() isn't supported because doc ids are emitted out of order");
-    }
-
-    private int advanceForExplainOnly(int target) throws IOException {
+    private Explanation explain(int target) throws IOException {
       int docId;
       do {
         docId = nextDocOutOfOrder();
@@ -283,17 +284,8 @@
         }
         docsEnum = null; // goto the next ord.
       } while (docId != DocIdSetIterator.NO_MORE_DOCS);
-      return docId;
-    }
 
-    @Override
-    public int freq() {
-      return 1;
-    }
-
-    @Override
-    public long cost() {
-      return cost;
+      return new ComplexExplanation(true, scores[ords[scoreUpto]], "Score based on join value " + termsEnum.term().utf8ToString());
     }
   }
 
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java
index ffb114d..180d310 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/ToChildBlockJoinQuery.java
@@ -124,11 +124,9 @@
     // NOTE: acceptDocs applies (and is checked) only in the
     // child document space
     @Override
-    public Scorer scorer(AtomicReaderContext readerContext, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext readerContext, Bits acceptDocs) throws IOException {
 
-      // Pass scoreDocsInOrder true, topScorer false to our sub:
-      final Scorer parentScorer = parentWeight.scorer(readerContext, true, false, null);
+      final Scorer parentScorer = parentWeight.scorer(readerContext, null);
 
       if (parentScorer == null) {
         // No matches
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
index 5e68f5e..9dbb2a9 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinCollector.java
@@ -325,46 +325,6 @@
     }
   }
 
-  private final static class FakeScorer extends Scorer {
-
-    float score;
-    int doc;
-
-    public FakeScorer() {
-      super((Weight) null);
-    }
-
-    @Override
-    public float score() {
-      return score;
-    }
-    
-    @Override
-    public int freq() {
-      return 1; // TODO: does anything else make sense?... duplicate of grouping's FakeScorer btw?
-    }
-
-    @Override
-    public int docID() {
-      return doc;
-    }
-
-    @Override
-    public int advance(int target) {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int nextDoc() {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public long cost() {
-      return 1;
-    }
-  }
-
   private OneGroup[] sortedGroups;
 
   private void sortQueue() {
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
index e5fe46d..f6985e2 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
@@ -17,6 +17,12 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Set;
+
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
@@ -35,12 +41,6 @@
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.FixedBitSet;
 
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Locale;
-import java.util.Set;
-
 /**
  * This query requires that you index
  * children and parent docs as a single block, using the
@@ -158,12 +158,9 @@
     // NOTE: acceptDocs applies (and is checked) only in the
     // parent document space
     @Override
-    public Scorer scorer(AtomicReaderContext readerContext, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext readerContext, Bits acceptDocs) throws IOException {
 
-      // Pass scoreDocsInOrder true, topScorer false to our sub and the live docs:
-      final Scorer childScorer = childWeight.scorer(readerContext, true, false, readerContext.reader().getLiveDocs());
-
+      final Scorer childScorer = childWeight.scorer(readerContext, readerContext.reader().getLiveDocs());
       if (childScorer == null) {
         // No matches
         return null;
@@ -195,7 +192,7 @@
 
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      BlockJoinScorer scorer = (BlockJoinScorer) scorer(context, true, false, context.reader().getLiveDocs());
+      BlockJoinScorer scorer = (BlockJoinScorer) scorer(context, context.reader().getLiveDocs());
       if (scorer != null && scorer.advance(doc) == doc) {
         return scorer.explain(context.docBase);
       }
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
index c7ddcdd..573e3ef 100644
--- a/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoin.java
@@ -1103,7 +1103,6 @@
     parentDoc = s.doc(qGroup.groupValue);
     assertEquals("Lisa", parentDoc.get("name"));
 
-
     r.close();
     dir.close();
   }
@@ -1126,7 +1125,7 @@
 
     ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(tq, parentFilter, ScoreMode.Avg);
     Weight weight = s.createNormalizedWeight(q);
-    DocIdSetIterator disi = weight.scorer(s.getIndexReader().leaves().get(0), true, true, null);
+    DocIdSetIterator disi = weight.scorer(s.getIndexReader().leaves().get(0), null);
     assertEquals(1, disi.advance(1));
     r.close();
     dir.close();
@@ -1160,7 +1159,7 @@
 
     ToParentBlockJoinQuery q = new ToParentBlockJoinQuery(tq, parentFilter, ScoreMode.Avg);
     Weight weight = s.createNormalizedWeight(q);
-    DocIdSetIterator disi = weight.scorer(s.getIndexReader().leaves().get(0), true, true, null);
+    DocIdSetIterator disi = weight.scorer(s.getIndexReader().leaves().get(0), null);
     assertEquals(2, disi.advance(0));
     r.close();
     dir.close();
@@ -1200,7 +1199,6 @@
     ToParentBlockJoinQuery childJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.Avg);
 
     ToParentBlockJoinCollector c = new ToParentBlockJoinCollector(Sort.RELEVANCE, 2, true, true);
-
     s.search(childJoinQuery, c);
 
     //Get all child documents within groups
@@ -1312,7 +1310,6 @@
     GroupDocs<Integer> group = groups.groups[0];
     StoredDocument doc = r.document(group.groupValue.intValue());
     assertEquals("0", doc.get("parentID"));
-    System.out.println("group: " + group);
 
     group = groups.groups[1];
     doc = r.document(group.groupValue.intValue());
@@ -1378,7 +1375,6 @@
     GroupDocs<Integer> group = groups.groups[0];
     StoredDocument doc = r.document(group.groupValue.intValue());
     assertEquals("0", doc.get("parentID"));
-    System.out.println("group: " + group);
 
     group = groups.groups[1];
     doc = r.document(group.groupValue.intValue());
diff --git a/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java b/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
index 94f784a..08c9c8d 100644
--- a/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
+++ b/lucene/join/src/test/org/apache/lucene/search/join/TestJoinUtil.java
@@ -17,6 +17,19 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
 import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.analysis.MockTokenizer;
 import org.apache.lucene.document.Document;
@@ -34,6 +47,8 @@
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Collector;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Explanation;
@@ -54,19 +69,6 @@
 import org.apache.lucene.util.TestUtil;
 import org.junit.Test;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
 public class TestJoinUtil extends LuceneTestCase {
 
   public void testSimple() throws Exception {
@@ -151,6 +153,104 @@
     dir.close();
   }
 
+  /** LUCENE-5487: verify a join query inside a SHOULD BQ
+   *  will still use the join query's optimized BulkScorers */
+  public void testInsideBooleanQuery() throws Exception {
+    final String idField = "id";
+    final String toField = "productId";
+
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(
+        random(),
+        dir,
+        newIndexWriterConfig(TEST_VERSION_CURRENT,
+            new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
+
+    // 0
+    Document doc = new Document();
+    doc.add(new TextField("description", "random text", Field.Store.NO));
+    doc.add(new TextField("name", "name1", Field.Store.NO));
+    doc.add(new TextField(idField, "7", Field.Store.NO));
+    w.addDocument(doc);
+
+    // 1
+    doc = new Document();
+    doc.add(new TextField("price", "10.0", Field.Store.NO));
+    doc.add(new TextField(idField, "2", Field.Store.NO));
+    doc.add(new TextField(toField, "7", Field.Store.NO));
+    w.addDocument(doc);
+
+    // 2
+    doc = new Document();
+    doc.add(new TextField("price", "20.0", Field.Store.NO));
+    doc.add(new TextField(idField, "3", Field.Store.NO));
+    doc.add(new TextField(toField, "7", Field.Store.NO));
+    w.addDocument(doc);
+
+    // 3
+    doc = new Document();
+    doc.add(new TextField("description", "more random text", Field.Store.NO));
+    doc.add(new TextField("name", "name2", Field.Store.NO));
+    doc.add(new TextField(idField, "0", Field.Store.NO));
+    w.addDocument(doc);
+    w.commit();
+
+    // 4
+    doc = new Document();
+    doc.add(new TextField("price", "10.0", Field.Store.NO));
+    doc.add(new TextField(idField, "5", Field.Store.NO));
+    doc.add(new TextField(toField, "0", Field.Store.NO));
+    w.addDocument(doc);
+
+    // 5
+    doc = new Document();
+    doc.add(new TextField("price", "20.0", Field.Store.NO));
+    doc.add(new TextField(idField, "6", Field.Store.NO));
+    doc.add(new TextField(toField, "0", Field.Store.NO));
+    w.addDocument(doc);
+
+    w.forceMerge(1);
+
+    IndexSearcher indexSearcher = new IndexSearcher(w.getReader());
+    w.close();
+
+    // Search for product
+    Query joinQuery =
+        JoinUtil.createJoinQuery(idField, false, toField, new TermQuery(new Term("description", "random")), indexSearcher, ScoreMode.Avg);
+
+    BooleanQuery bq = new BooleanQuery();
+    bq.add(joinQuery, BooleanClause.Occur.SHOULD);
+    bq.add(new TermQuery(new Term("id", "3")), BooleanClause.Occur.SHOULD);
+
+    indexSearcher.search(bq, new Collector() {
+        boolean sawFive;
+        @Override
+        public void setNextReader(AtomicReaderContext context) {
+        }
+        @Override
+        public void collect(int docID) {
+          // Hairy / evil (depends on how BooleanScorer
+          // stores temporarily collected docIDs by
+          // appending to head of linked list):
+          if (docID == 5) {
+            sawFive = true;
+          } else if (docID == 1) {
+            assertFalse("optimized bulkScorer was not used for join query embedded in boolean query!", sawFive);
+          }
+        }
+        @Override
+        public void setScorer(Scorer scorer) {
+        }
+        @Override
+        public boolean acceptsDocsOutOfOrder() {
+          return true;
+        }
+      });
+
+    indexSearcher.getIndexReader().close();
+    dir.close();
+  }
+
   public void testSimpleWithScoring() throws Exception {
     final String idField = "id";
     final String toField = "movieId";
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java b/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java
index 602fa8b..9861617 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java
@@ -234,20 +234,14 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
-      // Pass true for "scoresDocsInOrder", because we
-      // require in-order scoring, even if caller does not,
-      // since we call advance on the valSrcScorers.  Pass
-      // false for "topScorer" because we will not invoke
-      // score(Collector) on these scorers:
-      Scorer subQueryScorer = subQueryWeight.scorer(context, true, false, acceptDocs);
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
+      Scorer subQueryScorer = subQueryWeight.scorer(context, acceptDocs);
       if (subQueryScorer == null) {
         return null;
       }
       Scorer[] valSrcScorers = new Scorer[valSrcWeights.length];
       for(int i = 0; i < valSrcScorers.length; i++) {
-         valSrcScorers[i] = valSrcWeights[i].scorer(context, true, topScorer, acceptDocs);
+         valSrcScorers[i] = valSrcWeights[i].scorer(context, acceptDocs);
       }
       return new CustomScorer(CustomScoreQuery.this.getCustomScoreProvider(context), this, queryWeight, subQueryScorer, valSrcScorers);
     }
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/BoostedQuery.java b/lucene/queries/src/java/org/apache/lucene/queries/function/BoostedQuery.java
index ba3163e..7ba5f4f 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/BoostedQuery.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/BoostedQuery.java
@@ -97,11 +97,9 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
-      // we are gonna advance() the subscorer
-      Scorer subQueryScorer = qWeight.scorer(context, true, false, acceptDocs);
-      if(subQueryScorer == null) {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
+      Scorer subQueryScorer = qWeight.scorer(context, acceptDocs);
+      if (subQueryScorer == null) {
         return null;
       }
       return new BoostedQuery.CustomScorer(context, this, getBoost(), subQueryScorer, boostVal);
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java
index 6c44b05..726b97e 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java
@@ -90,14 +90,13 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       return new AllScorer(context, acceptDocs, this, queryWeight);
     }
 
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      return ((AllScorer)scorer(context, true, true, context.reader().getLiveDocs())).explain(doc);
+      return ((AllScorer)scorer(context, context.reader().getLiveDocs())).explain(doc);
     }
   }
 
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/QueryValueSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/QueryValueSource.java
index 3e22e8e..10a5f0d 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/QueryValueSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/QueryValueSource.java
@@ -123,7 +123,7 @@
     try {
       if (doc < lastDocRequested) {
         if (noMatches) return defVal;
-        scorer = weight.scorer(readerContext, true, false, acceptDocs);
+        scorer = weight.scorer(readerContext, acceptDocs);
         if (scorer==null) {
           noMatches = true;
           return defVal;
@@ -154,7 +154,7 @@
     try {
       if (doc < lastDocRequested) {
         if (noMatches) return false;
-        scorer = weight.scorer(readerContext, true, false, acceptDocs);
+        scorer = weight.scorer(readerContext, acceptDocs);
         scorerDoc = -1;
         if (scorer==null) {
           noMatches = true;
@@ -212,7 +212,7 @@
             mval.exists = false;
             return;
           }
-          scorer = weight.scorer(readerContext, true, false, acceptDocs);
+          scorer = weight.scorer(readerContext, acceptDocs);
           scorerDoc = -1;
           if (scorer==null) {
             noMatches = true;
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingBulkOutOfOrderScorer.java b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingBulkOutOfOrderScorer.java
new file mode 100644
index 0000000..0b2fa34
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingBulkOutOfOrderScorer.java
@@ -0,0 +1,110 @@
+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.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Random;
+import java.util.WeakHashMap;
+
+import org.apache.lucene.index.DocsEnum;
+import org.apache.lucene.util.VirtualMethod;
+
+/** A crazy {@link BulkScorer} that wraps a {@link Scorer}
+ *  but shuffles the order of the collected documents. */
+public class AssertingBulkOutOfOrderScorer extends BulkScorer {
+
+  final Random random;
+  final Scorer scorer;
+
+  public AssertingBulkOutOfOrderScorer(Random random, Scorer scorer) {
+    this.random = random;
+    this.scorer = scorer;
+  }
+
+  private void shuffle(int[] docIDs, float[] scores, int[] freqs, int size) {
+    for (int i = size - 1; i > 0; --i) {
+      final int other = random.nextInt(i + 1);
+
+      final int tmpDoc = docIDs[i];
+      docIDs[i] = docIDs[other];
+      docIDs[other] = tmpDoc;
+
+      final float tmpScore = scores[i];
+      scores[i] = scores[other];
+      scores[other] = tmpScore;
+      
+      final int tmpFreq = freqs[i];
+      freqs[i] = freqs[other];
+      freqs[other] = tmpFreq;
+    }
+  }
+
+  private static void flush(int[] docIDs, float[] scores, int[] freqs, int size,
+      FakeScorer scorer, Collector collector) throws IOException {
+    for (int i = 0; i < size; ++i) {
+      scorer.doc = docIDs[i];
+      scorer.freq = freqs[i];
+      scorer.score = scores[i];
+      collector.collect(scorer.doc);
+    }
+  }
+
+  @Override
+  public boolean score(Collector collector, int max) throws IOException {
+    if (scorer.docID() == -1) {
+      scorer.nextDoc();
+    }
+
+    FakeScorer fake = new FakeScorer();
+    collector.setScorer(fake);
+
+    final int bufferSize = 1 + random.nextInt(100);
+    final int[] docIDs = new int[bufferSize];
+    final float[] scores = new float[bufferSize];
+    final int[] freqs = new int[bufferSize];
+
+    int buffered = 0;
+    int doc = scorer.docID();
+    while (doc < max) {
+      docIDs[buffered] = doc;
+      scores[buffered] = scorer.score();
+      freqs[buffered] = scorer.freq();
+
+      if (++buffered == bufferSize) {
+        shuffle(docIDs, scores, freqs, buffered);
+        flush(docIDs, scores, freqs, buffered, fake, collector);
+        buffered = 0;
+      }
+      doc = scorer.nextDoc();
+    }
+
+    shuffle(docIDs, scores, freqs, buffered);
+    flush(docIDs, scores, freqs, buffered, fake, collector);
+
+    return doc != Scorer.NO_MORE_DOCS;
+  }
+
+  @Override
+  public String toString() {
+    return "AssertingBulkOutOfOrderScorer(" + scorer + ")";
+  }
+}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingBulkScorer.java b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingBulkScorer.java
new file mode 100644
index 0000000..995f49a
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingBulkScorer.java
@@ -0,0 +1,84 @@
+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.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Random;
+import java.util.WeakHashMap;
+
+import org.apache.lucene.index.DocsEnum;
+import org.apache.lucene.util.VirtualMethod;
+
+/** Wraps a Scorer with additional checks */
+public class AssertingBulkScorer extends BulkScorer {
+
+  private static final VirtualMethod<BulkScorer> SCORE_COLLECTOR = new VirtualMethod<BulkScorer>(BulkScorer.class, "score", Collector.class);
+  private static final VirtualMethod<BulkScorer> SCORE_COLLECTOR_RANGE = new VirtualMethod<BulkScorer>(BulkScorer.class, "score", Collector.class, int.class);
+
+  public static BulkScorer wrap(Random random, BulkScorer other) {
+    if (other == null || other instanceof AssertingBulkScorer) {
+      return other;
+    }
+    return new AssertingBulkScorer(random, other);
+  }
+
+  public static boolean shouldWrap(BulkScorer inScorer) {
+    return SCORE_COLLECTOR.isOverriddenAsOf(inScorer.getClass()) || SCORE_COLLECTOR_RANGE.isOverriddenAsOf(inScorer.getClass());
+  }
+
+  final Random random;
+  final BulkScorer in;
+
+  private AssertingBulkScorer(Random random, BulkScorer in) {
+    this.random = random;
+    this.in = in;
+  }
+
+  public BulkScorer getIn() {
+    return in;
+  }
+
+  @Override
+  public void score(Collector collector) throws IOException {
+    if (random.nextBoolean()) {
+      try {
+        final boolean remaining = in.score(collector, DocsEnum.NO_MORE_DOCS);
+        assert !remaining;
+      } catch (UnsupportedOperationException e) {
+        in.score(collector);
+      }
+    } else {
+      in.score(collector);
+    }
+  }
+
+  @Override
+  public boolean score(Collector collector, int max) throws IOException {
+    return in.score(collector, max);
+  }
+
+  @Override
+  public String toString() {
+    return "AssertingBulkScorer(" + in + ")";
+  }
+
+}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingIndexSearcher.java b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingIndexSearcher.java
index eef42b0..47725db 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingIndexSearcher.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingIndexSearcher.java
@@ -90,6 +90,7 @@
 
   @Override
   protected void search(List<AtomicReaderContext> leaves, Weight weight, Collector collector) throws IOException {
+    // TODO: shouldn't we AssertingCollector.wrap(collector) here?
     super.search(leaves, AssertingWeight.wrap(random, weight), collector);
   }
 
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java
index 22a73c1..b55cf63 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java
@@ -32,31 +32,20 @@
 /** Wraps a Scorer with additional checks */
 public class AssertingScorer extends Scorer {
 
-  enum TopScorer {
-    YES, NO, UNKNOWN;
-  }
-
-  private static final VirtualMethod<Scorer> SCORE_COLLECTOR = new VirtualMethod<Scorer>(Scorer.class, "score", Collector.class);
-  private static final VirtualMethod<Scorer> SCORE_COLLECTOR_RANGE = new VirtualMethod<Scorer>(Scorer.class, "score", Collector.class, int.class, int.class);
-
   // we need to track scorers using a weak hash map because otherwise we
   // could loose references because of eg.
   // AssertingScorer.score(Collector) which needs to delegate to work correctly
   private static Map<Scorer, WeakReference<AssertingScorer>> ASSERTING_INSTANCES = Collections.synchronizedMap(new WeakHashMap<Scorer, WeakReference<AssertingScorer>>());
 
-  private static Scorer wrap(Random random, Scorer other, TopScorer topScorer, boolean inOrder) {
+  public static Scorer wrap(Random random, Scorer other) {
     if (other == null || other instanceof AssertingScorer) {
       return other;
     }
-    final AssertingScorer assertScorer = new AssertingScorer(random, other, topScorer, inOrder);
+    final AssertingScorer assertScorer = new AssertingScorer(random, other);
     ASSERTING_INSTANCES.put(other, new WeakReference<AssertingScorer>(assertScorer));
     return assertScorer;
   }
 
-  static Scorer wrap(Random random, Scorer other, boolean topScorer, boolean inOrder) {
-    return wrap(random, other, topScorer ? TopScorer.YES : TopScorer.NO, inOrder);
-  }
-
   static Scorer getAssertingScorer(Random random, Scorer other) {
     if (other == null || other instanceof AssertingScorer) {
       return other;
@@ -68,7 +57,7 @@
       // scorer1.score(collector) calls
       // collector.setScorer(scorer2) with scorer1 != scorer2, such as
       // BooleanScorer. In that case we can't enable all assertions
-      return new AssertingScorer(random, other, TopScorer.UNKNOWN, false);
+      return new AssertingScorer(random, other);
     } else {
       return assertingScorer;
     }
@@ -77,20 +66,12 @@
   final Random random;
   final Scorer in;
   final AssertingAtomicReader.AssertingDocsEnum docsEnumIn;
-  final TopScorer topScorer;
-  final boolean inOrder;
-  final boolean canCallNextDoc;
 
-  private AssertingScorer(Random random, Scorer in, TopScorer topScorer, boolean inOrder) {
+  private AssertingScorer(Random random, Scorer in) {
     super(in.weight);
     this.random = random;
     this.in = in;
-    this.topScorer = topScorer;
-    this.inOrder = inOrder;
-    this.docsEnumIn = new AssertingAtomicReader.AssertingDocsEnum(in, topScorer == TopScorer.NO);
-    this.canCallNextDoc = topScorer != TopScorer.YES // not a top scorer
-      || !SCORE_COLLECTOR_RANGE.isOverriddenAsOf(in.getClass()) // the default impl relies upon nextDoc()
-      || !SCORE_COLLECTOR.isOverriddenAsOf(in.getClass()); // the default impl relies upon nextDoc()
+    this.docsEnumIn = new AssertingAtomicReader.AssertingDocsEnum(in);
   }
 
   public Scorer getIn() {
@@ -116,143 +97,6 @@
     return score;
   }
 
-  private final static class FakeScorer extends Scorer {
-
-    float score;
-    int doc;
-    int freq;
-    final long cost;
-
-    public FakeScorer(Scorer other) {
-      super((Weight) null);
-      this.cost = other.cost();
-    }
-
-    @Override
-    public float score() {
-      return score;
-    }
-
-    @Override
-    public int freq() {
-      return freq;
-    }
-
-    @Override
-    public int docID() {
-      return doc;
-    }
-
-    @Override
-    public int advance(int target) {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int nextDoc() {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public long cost() {
-      return cost;
-    }
-  }
-
-  private void shuffle(int[] docIDs, float[] scores, int[] freqs, int size) {
-    for (int i = size - 1; i > 0; --i) {
-      final int other = random.nextInt(i + 1);
-
-      final int tmpDoc = docIDs[i];
-      docIDs[i] = docIDs[other];
-      docIDs[other] = tmpDoc;
-
-      final float tmpScore = scores[i];
-      scores[i] = scores[other];
-      scores[other] = tmpScore;
-      
-      final int tmpFreq = freqs[i];
-      freqs[i] = freqs[other];
-      freqs[other] = tmpFreq;
-    }
-  }
-
-  private static void flush(int[] docIDs, float[] scores, int[] freqs, int size,
-      FakeScorer scorer, Collector collector) throws IOException {
-    for (int i = 0; i < size; ++i) {
-      scorer.doc = docIDs[i];
-      scorer.freq = freqs[i];
-      scorer.score = scores[i];
-      collector.collect(scorer.doc);
-    }
-  }
-
-  private void scoreInRandomOrder(Collector collector) throws IOException {
-    assert docID() == -1; // not started
-    FakeScorer fake = new FakeScorer(this);
-    collector.setScorer(fake);
-
-    final int bufferSize = 1 + random.nextInt(100);
-    final int[] docIDs = new int[bufferSize];
-    final float[] scores = new float[bufferSize];
-    final int[] freqs = new int[bufferSize];
-
-    int buffered = 0;
-    int doc;
-    while ((doc = nextDoc()) != NO_MORE_DOCS) {
-      docIDs[buffered] = doc;
-      scores[buffered] = score();
-      freqs[buffered] = freq();
-
-      if (++buffered == bufferSize) {
-        shuffle(docIDs, scores, freqs, buffered);
-        flush(docIDs, scores, freqs, buffered, fake, collector);
-        buffered = 0;
-      }
-    }
-
-    shuffle(docIDs, scores, freqs, buffered);
-    flush(docIDs, scores, freqs, buffered, fake, collector);
-  }
-
-
-  @Override
-  public void score(Collector collector) throws IOException {
-    assert topScorer != TopScorer.NO;
-    if (SCORE_COLLECTOR.isOverriddenAsOf(this.in.getClass())) {
-      if (random.nextBoolean()) {
-        try {
-          final boolean remaining = in.score(collector, DocsEnum.NO_MORE_DOCS, in.nextDoc());
-          assert !remaining;
-        } catch (UnsupportedOperationException e) {
-          in.score(collector);
-        }
-      } else {
-        in.score(collector);
-      }
-    } else {
-      // score(Collector) has not been overridden, use the super method in
-      // order to benefit from all assertions
-      if (collector.acceptsDocsOutOfOrder() && random.nextBoolean()) {
-        scoreInRandomOrder(collector);
-      } else {
-        super.score(collector);
-      }
-    }
-  }
-
-  @Override
-  public boolean score(Collector collector, int max, int firstDocID) throws IOException {
-    assert topScorer != TopScorer.NO;
-    if (SCORE_COLLECTOR_RANGE.isOverriddenAsOf(this.in.getClass())) {
-      return in.score(collector, max, firstDocID);
-    } else {
-      // score(Collector,int,int) has not been overridden, use the super
-      // method in order to benefit from all assertions
-      return super.score(collector, max, firstDocID);
-    }
-  }
-
   @Override
   public Collection<ChildScorer> getChildren() {
     // We cannot hide that we hold a single child, else
@@ -275,13 +119,11 @@
 
   @Override
   public int nextDoc() throws IOException {
-    assert canCallNextDoc : "top scorers should not call nextDoc()";
     return docsEnumIn.nextDoc();
   }
 
   @Override
   public int advance(int target) throws IOException {
-    assert canCallNextDoc : "top scorers should not call advance(target)";
     return docsEnumIn.advance(target);
   }
 
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java
index c712b59..793b396 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/AssertingWeight.java
@@ -60,19 +60,46 @@
   }
 
   @Override
-  public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-                       boolean topScorer, Bits acceptDocs) throws IOException {
+  public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
     // if the caller asks for in-order scoring or if the weight does not support
     // out-of order scoring then collection will have to happen in-order.
-    final boolean inOrder = scoreDocsInOrder || !scoresDocsOutOfOrder();
-    final Scorer inScorer = in.scorer(context, scoreDocsInOrder, topScorer, acceptDocs);
-    return AssertingScorer.wrap(new Random(random.nextLong()), inScorer, topScorer, inOrder);
+    final Scorer inScorer = in.scorer(context, acceptDocs);
+    return AssertingScorer.wrap(new Random(random.nextLong()), inScorer);
+  }
+
+  @Override
+  public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder, Bits acceptDocs) throws IOException {
+    // if the caller asks for in-order scoring or if the weight does not support
+    // out-of order scoring then collection will have to happen in-order.
+    BulkScorer inScorer = in.bulkScorer(context, scoreDocsInOrder, acceptDocs);
+    if (inScorer == null) {
+      return null;
+    }
+
+    if (AssertingBulkScorer.shouldWrap(inScorer)) {
+      // The incoming scorer already has a specialized
+      // implementation for BulkScorer, so we should use it:
+      return AssertingBulkScorer.wrap(new Random(random.nextLong()), inScorer);
+    } else if (scoreDocsInOrder == false && random.nextBoolean()) {
+      // The caller claims it can handle out-of-order
+      // docs; let's confirm that by pulling docs and
+      // randomly shuffling them before collection:
+      //Scorer scorer = in.scorer(context, acceptDocs);
+      Scorer scorer = scorer(context, acceptDocs);
+
+      // Scorer should not be null if bulkScorer wasn't:
+      assert scorer != null;
+      return new AssertingBulkOutOfOrderScorer(new Random(random.nextLong()), scorer);
+    } else {
+      // Let super wrap this.scorer instead, so we use
+      // AssertingScorer:
+      return super.bulkScorer(context, scoreDocsInOrder, acceptDocs);
+    }
   }
 
   @Override
   public boolean scoresDocsOutOfOrder() {
     return scoresDocsOutOfOrder;
   }
-
 }
 
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/QueryUtils.java b/lucene/test-framework/src/java/org/apache/lucene/search/QueryUtils.java
index 0679be8..cd00f74 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/search/QueryUtils.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/QueryUtils.java
@@ -267,7 +267,7 @@
               if (scorer == null) {
                 Weight w = s.createNormalizedWeight(q);
                 AtomicReaderContext context = readerContextArray.get(leafPtr);
-                scorer = w.scorer(context, true, false, context.reader().getLiveDocs());
+                scorer = w.scorer(context, context.reader().getLiveDocs());
               }
               
               int op = order[(opidx[0]++) % order.length];
@@ -314,7 +314,7 @@
               indexSearcher.setSimilarity(s.getSimilarity());
               Weight w = indexSearcher.createNormalizedWeight(q);
               AtomicReaderContext ctx = (AtomicReaderContext)indexSearcher.getTopReaderContext();
-              Scorer scorer = w.scorer(ctx, true, false, ctx.reader().getLiveDocs());
+              Scorer scorer = w.scorer(ctx, ctx.reader().getLiveDocs());
               if (scorer != null) {
                 boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
                 Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
@@ -341,7 +341,7 @@
           indexSearcher.setSimilarity(s.getSimilarity());
           Weight w = indexSearcher.createNormalizedWeight(q);
           AtomicReaderContext ctx = previousReader.getContext();
-          Scorer scorer = w.scorer(ctx, true, false, ctx.reader().getLiveDocs());
+          Scorer scorer = w.scorer(ctx, ctx.reader().getLiveDocs());
           if (scorer != null) {
             boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
             Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
@@ -372,7 +372,7 @@
           long startMS = System.currentTimeMillis();
           for (int i=lastDoc[0]+1; i<=doc; i++) {
             Weight w = s.createNormalizedWeight(q);
-            Scorer scorer = w.scorer(context.get(leafPtr), true, false, liveDocs);
+            Scorer scorer = w.scorer(context.get(leafPtr), liveDocs);
             Assert.assertTrue("query collected "+doc+" but skipTo("+i+") says no more docs!",scorer.advance(i) != DocIdSetIterator.NO_MORE_DOCS);
             Assert.assertEquals("query collected "+doc+" but skipTo("+i+") got to "+scorer.docID(),doc,scorer.docID());
             float skipToScore = scorer.score();
@@ -400,7 +400,7 @@
           IndexSearcher indexSearcher = LuceneTestCase.newSearcher(previousReader);
           indexSearcher.setSimilarity(s.getSimilarity());
           Weight w = indexSearcher.createNormalizedWeight(q);
-          Scorer scorer = w.scorer((AtomicReaderContext)indexSearcher.getTopReaderContext(), true, false, previousReader.getLiveDocs());
+          Scorer scorer = w.scorer((AtomicReaderContext)indexSearcher.getTopReaderContext(), previousReader.getLiveDocs());
           if (scorer != null) {
             boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
             Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
@@ -425,7 +425,7 @@
       IndexSearcher indexSearcher = LuceneTestCase.newSearcher(previousReader);
       indexSearcher.setSimilarity(s.getSimilarity());
       Weight w = indexSearcher.createNormalizedWeight(q);
-      Scorer scorer = w.scorer((AtomicReaderContext)indexSearcher.getTopReaderContext(), true, false, previousReader.getLiveDocs());
+      Scorer scorer = w.scorer((AtomicReaderContext)indexSearcher.getTopReaderContext(), previousReader.getLiveDocs());
       if (scorer != null) {
         boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
         Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
index e2c5ba8..71f4e45 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
@@ -17,6 +17,21 @@
 
 package org.apache.solr.handler.component;
 
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.ReaderUtil;
@@ -28,6 +43,7 @@
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.Weight;
 import org.apache.lucene.search.grouping.GroupDocs;
 import org.apache.lucene.search.grouping.SearchGroup;
 import org.apache.lucene.search.grouping.TopGroups;
@@ -83,22 +99,6 @@
 import org.apache.solr.search.grouping.endresulttransformer.SimpleEndResultTransformer;
 import org.apache.solr.util.SolrPluginUtils;
 
-import org.apache.commons.lang.StringUtils;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 /**
  * TODO!
  * 
@@ -1227,5 +1227,15 @@
     public long cost() {
       return 1;
     }
+
+    @Override
+    public Weight getWeight() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<ChildScorer> getChildren() {
+      throw new UnsupportedOperationException();
+    }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/schema/LatLonType.java b/solr/core/src/java/org/apache/solr/schema/LatLonType.java
index 51b186b..bfc9b48 100644
--- a/solr/core/src/java/org/apache/solr/schema/LatLonType.java
+++ b/solr/core/src/java/org/apache/solr/schema/LatLonType.java
@@ -333,14 +333,13 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       return new SpatialScorer(context, acceptDocs, this, queryWeight);
     }
 
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      return ((SpatialScorer)scorer(context, true, true, context.reader().getLiveDocs())).explain(doc);
+      return ((SpatialScorer)scorer(context, context.reader().getLiveDocs())).explain(doc);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java
index 6c451fd..f410fc1 100644
--- a/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java
@@ -232,8 +232,7 @@
 
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       if (filter == null) {
         boolean debug = rb != null && rb.isDebug();
         long start = debug ? System.currentTimeMillis() : 0;
@@ -502,7 +501,7 @@
 
     @Override
     public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
-      Scorer scorer = scorer(context, true, false, context.reader().getLiveDocs());
+      Scorer scorer = scorer(context, context.reader().getLiveDocs());
       boolean exists = scorer.advance(doc) == doc;
 
       ComplexExplanation result = new ComplexExplanation();
diff --git a/solr/core/src/java/org/apache/solr/search/SolrConstantScoreQuery.java b/solr/core/src/java/org/apache/solr/search/SolrConstantScoreQuery.java
index 2b6268e..dfdf7ad 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrConstantScoreQuery.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrConstantScoreQuery.java
@@ -119,8 +119,7 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
-        boolean topScorer, Bits acceptDocs) throws IOException {
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
       return new ConstantScorer(context, this, queryWeight, acceptDocs);
     }
 
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 22a7360..e7007ef 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -2472,7 +2472,7 @@
         iterators.add(iter);
       }
       for (Weight w : weights) {
-        Scorer scorer = w.scorer(context, true, false, context.reader().getLiveDocs());
+        Scorer scorer = w.scorer(context, context.reader().getLiveDocs());
         if (scorer == null) return null;
         iterators.add(scorer);
       }
diff --git a/solr/core/src/java/org/apache/solr/search/join/IgnoreAcceptDocsQuery.java b/solr/core/src/java/org/apache/solr/search/join/IgnoreAcceptDocsQuery.java
index 7e057fe..d61b4ce 100644
--- a/solr/core/src/java/org/apache/solr/search/join/IgnoreAcceptDocsQuery.java
+++ b/solr/core/src/java/org/apache/solr/search/join/IgnoreAcceptDocsQuery.java
@@ -86,8 +86,8 @@
     }
 
     @Override
-    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, Bits acceptDocs) throws IOException {
-      return w.scorer(context, scoreDocsInOrder, topScorer, null);
+    public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException {
+      return w.scorer(context, null);
     }
   }