diff --git a/lucene/core/src/java/org/apache/lucene/search/BooleanArrayScorer.java b/lucene/core/src/java/org/apache/lucene/search/BooleanArrayScorer.java
new file mode 100644
index 0000000..87197f1
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanArrayScorer.java
@@ -0,0 +1,334 @@
+package org.apache.lucene.search;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.lucene.search.BooleanQuery.BooleanWeight;
+
+/**
+ *  This is an improvement of {@link BooleanScorer}.
+ *  It only supports cases where there is at least one MUST clause.
+ */
+final class BooleanArrayScorer extends Scorer {
+
+  private static final class Bucket {
+    int doc;                 // doc id
+    // score is divided into RS and OS, so that its calculating order
+    // can be the same as the DAAT procedure.
+    double requiredScore;    // incremental required score
+    double optionalScore;    // incremental optional score
+    int coord;               // count of terms in score
+    boolean valid;           // valid bucket
+  }
+
+  /** A simple hash table of document scores within a range. */
+  private final class BucketTable {
+    static final int SIZE = 1 << 8;
+
+    private final Bucket[] buckets = new Bucket[SIZE];
+    // After collecting more documents, if there are more documents not collected.
+    boolean more = true;
+    private int numOfBuckets = 0;
+    private int numOfValidBuckets = 0;
+
+    BucketTable() {
+      // Pre-fill to save the lazy init when collecting
+      // each sub:
+      for(int idx=0;idx<SIZE;idx++) {
+        buckets[idx] = new Bucket();
+      }
+    }
+
+    /**
+     * Force to collect more docs.
+     * It is not possible that more is true, while first is null.
+     * <code>more</code> will be true if more matching documents may remain.
+     */
+    void collectMoreForce() throws IOException {
+      do {
+        // If there are more docs not collected, but no doc is collected in this iteration,
+        // collect more again.
+        collectMore();
+      } while (more && numOfValidBuckets == 0);
+    }
+
+    /**
+     * Collect more docs to bucket table. After calling this method,
+     * <code>more</code> will be true if more matching documents may remain.
+     */
+    void collectMore() throws IOException {
+      numOfBuckets = 0;
+
+      // Scan requiredDocs full fill the  bucket table
+      while (numOfBuckets < SIZE) {
+        final int requiredDocID = requiredScorer.nextDoc();
+        if (requiredDocID == DocIdSetIterator.NO_MORE_DOCS) {
+          more = false;
+          break;
+        }
+        final Bucket bucket = buckets[numOfBuckets ++];
+        bucket.doc = requiredDocID;
+        bucket.coord = requiredNrMatch;
+        bucket.requiredScore = requiredScorer.score();
+        bucket.optionalScore = 0;
+        bucket.valid = true;
+      }
+      numOfValidBuckets = numOfBuckets;
+
+      // Scan prohibitedDocs to remove docs from bucket table.
+      for (Scorer prohibitedScorer : prohibitedScorers) {
+        int i = 0;
+        while (i < numOfBuckets) {
+          final Bucket bucket = buckets[i];
+          int prohibitedDocID = prohibitedScorer.docID();
+          if (prohibitedDocID < bucket.doc) {
+            // According to the definition of .advance(),
+            // prohibitedDocID should be less then bucket.doc,
+            // before calling .advance(bucket.doc);
+
+            // Skip to bucket.doc, so that prohibitedDocID >= bucket.doc
+            prohibitedDocID = prohibitedScorer.advance(bucket.doc);
+          }
+
+          if (prohibitedDocID == DocIdSetIterator.NO_MORE_DOCS) {
+            break;
+
+          } else if (prohibitedDocID == bucket.doc) {
+            // remove the prohibited bucket.
+            if (bucket.valid) {
+              numOfValidBuckets --;
+              bucket.valid = false;
+            }
+            i ++;
+
+          } else { // prohibitedDocID > bucket.doc
+            i = skipsTo(i, prohibitedDocID);
+          }
+        }
+      }
+
+      // Scan optionalDocs to add coord and score.
+      // TODO: use countLeft to judge whether a bucket should be removed.
+//      int countLeft = optionalScorers.size();
+      for (Scorer optionalScorer : optionalScorers) {
+//        countLeft --;
+        int i = 0;
+        while (i < numOfBuckets) {
+          final Bucket bucket = buckets[i];
+          int optionalDocID = optionalScorer.docID();
+          if (optionalDocID < bucket.doc) {
+            // According to the definition of .advance(),
+            // optionalDocID should be less then bucket.doc,
+            // before calling .advance(bucket.doc);
+
+            // Skip to bucket.doc, so that optionalDocID >= bucket.doc
+            optionalDocID = optionalScorer.advance(bucket.doc);
+          }
+
+          if (optionalDocID == DocIdSetIterator.NO_MORE_DOCS) {
+            break;
+
+          } else if (optionalDocID == bucket.doc) {
+            bucket.coord ++;
+            bucket.optionalScore += optionalScorer.score();
+            i ++;
+//            if (oldBucket.coord + countLeft < minNrShouldMatch) remove(oldBucket);
+
+          } else { // optionalDocID > bucket.doc
+            // current bucket skips to optionalDocID.
+            i = skipsTo(i, optionalDocID);
+          }
+        }
+      }
+    }
+
+    /**
+     * Skips to target from begin. <br/>
+     * <b>NOTE:</b> <b>Undefined</b> when buckets[i].doc >= target.
+     * @param begin the index begin to skip, <b>MUST</b> s.t. -1 &le; begin.
+     * @param target the target doc to skip.
+     * @return the first index i s.t. buckets[i].doc >= target.
+     */
+    int skipsTo(int begin, int target) {
+      final int DELTA = 16;
+      int i = begin + DELTA;
+      while (i < numOfBuckets && buckets[i].doc < target) {
+        i += DELTA;
+      }
+
+      int end = i;
+      if (end >= numOfBuckets) {
+        end = numOfBuckets;
+
+      } else if (buckets[end].doc == target) {
+        return end;
+      }
+
+      i = i - DELTA + 1;
+      while (i < end && buckets[i].doc < target) {
+        i ++;
+      }
+      return i;
+    }
+
+    void advance(int target) throws IOException {
+      // advance requiredConjunctionScorer to target doc
+      if (requiredScorer.docID() < target) {
+        requiredScorer.advance(target);
+      }
+
+      // Scan prohibitedDocs to advance to target doc
+      for (Scorer prohibitedScorer : prohibitedScorers) {
+        if (prohibitedScorer.docID() < target) {
+          prohibitedScorer.advance(target);
+        }
+      }
+
+      // Scan optionalDocs to advance to target doc
+      for (Scorer optionalScorer : optionalScorers) {
+        if (optionalScorer.docID() < target) {
+          optionalScorer.advance(target);
+        }
+      }
+    }
+  }
+
+  private final BucketTable bucketTable = new BucketTable();
+  private final float[] coordFactors;
+  private int currentIndex = -1;
+  private int currentDoc = -1;
+
+  final private Scorer requiredScorer;
+  final int requiredNrMatch;
+//  final private List<Scorer> requiredScorers;
+  final private List<Scorer> optionalScorers;
+  final private List<Scorer> prohibitedScorers;
+  // minNrShouldMatch only applies to SHOULD clauses
+  final private int minNrShouldMatch;
+
+  BooleanArrayScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
+      List<Scorer> requiredScorers, List<Scorer> optionalScorers, List<Scorer> prohibitedScorers,
+      int maxCoord) throws IOException {
+    this(weight, disableCoord, minNrShouldMatch,
+        requiredScorers.isEmpty() ? null :
+          new ConjunctionScorer(weight, requiredScorers.toArray(new Scorer[requiredScorers.size()])),
+        requiredScorers.size(),
+        optionalScorers, prohibitedScorers, maxCoord);
+  }
+
+  BooleanArrayScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
+      Scorer requiredScorer, int requiredNrMatch, List<Scorer> optionalScorers, List<Scorer> prohibitedScorers,
+      int maxCoord) throws IOException {
+    super(weight);
+    if (requiredNrMatch <= 0) {
+      throw new IllegalArgumentException("requriedScorers.size() must be > 0");
+    }
+    if (minNrShouldMatch < 0) {
+      throw new IllegalArgumentException("Minimum number of optional scorers should not be negative");
+    }
+    this.minNrShouldMatch = minNrShouldMatch;
+
+    this.requiredScorer = requiredScorer;
+    this.requiredNrMatch = requiredNrMatch;
+    this.optionalScorers = optionalScorers;
+    this.prohibitedScorers = prohibitedScorers;
+
+    coordFactors = new float[requiredNrMatch + optionalScorers.size() + 1];
+    for (int i = 0; i < coordFactors.length; i++) {
+      coordFactors[i] = disableCoord ? 1.0f : weight.coord(i, maxCoord);
+    }
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer buffer = new StringBuffer();
+    buffer.append("boolean(");
+    buffer.append("+");
+    buffer.append(requiredScorer.toString());
+    buffer.append(" ");
+    for (Scorer optionalScorer : optionalScorers) {
+      buffer.append(optionalScorer.toString());
+      buffer.append(" ");
+    }
+    for (Scorer prohibitedScorer : prohibitedScorers) {
+      buffer.append("-");
+      buffer.append(prohibitedScorer.toString());
+      buffer.append(" ");
+    }
+    // Delete the last whitespace
+    if (buffer.length() > "boolean(".length()) {
+      buffer.deleteCharAt(buffer.length() - 1);
+    }
+    buffer.append(")");
+    return buffer.toString();
+  }
+
+  @Override
+  public float score() throws IOException {
+    final Bucket bucket = bucketTable.buckets[currentIndex];
+    // Cast required score and optional score to float,
+    // in order to make the calculating procedure is the same as DAAT.
+    return (float) ((float) bucket.requiredScore + (float) bucket.optionalScore) * coordFactors[bucket.coord];
+  }
+
+  @Override
+  public int freq() throws IOException {
+    return bucketTable.buckets[currentIndex].coord;
+  }
+
+  @Override
+  public int docID() {
+    return currentDoc;
+  }
+
+  @Override
+  public int nextDoc() throws IOException {
+    currentIndex ++;
+    if (bucketTable.more && currentIndex >= bucketTable.numOfBuckets) {
+      bucketTable.collectMoreForce();
+      currentIndex = 0;
+    }
+
+    while (currentIndex < bucketTable.numOfBuckets) {
+      final Bucket bucket = bucketTable.buckets[currentIndex];
+
+      if (bucket.valid && bucket.coord - requiredNrMatch >= minNrShouldMatch) {
+        return currentDoc = bucket.doc;
+      }
+
+      currentIndex ++;
+      if (bucketTable.more && currentIndex >= bucketTable.numOfBuckets) {
+        bucketTable.collectMoreForce();
+        currentIndex = 0;
+      }
+    }
+    return currentDoc = DocIdSetIterator.NO_MORE_DOCS;
+  }
+
+  @Override
+  public int advance(int target) throws IOException {
+    return slowAdvance(target);
+  }
+
+  @Override
+  public long cost() {
+    return requiredScorer.cost();
+  }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/search/BooleanLinkedScorer.java b/lucene/core/src/java/org/apache/lucene/search/BooleanLinkedScorer.java
new file mode 100644
index 0000000..32a37f1
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanLinkedScorer.java
@@ -0,0 +1,381 @@
+package org.apache.lucene.search;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.lucene.search.BooleanQuery.BooleanWeight;
+import org.apache.lucene.util.FixedBitSet;
+
+/**
+ *  This is an improvement of {@link BooleanScorer}.
+ *  It only supports cases where there is at least one MUST clause.
+ */
+final class BooleanLinkedScorer extends Scorer {
+
+
+  private static final class Bucket {
+    int doc;                 // doc id
+    // score is divided into RS and OS, so that its calculating order
+    // can be the same as the DAAT procedure.
+    double requiredScore;    // incremental required score
+    double optionalScore;    // incremental optional score
+    int coord;               // count of terms in score
+    Bucket next;             // next valid bucket
+    Bucket prev;             // previous valid bucket
+  }
+
+  /** A simple hash table of document scores within a range. */
+  private final class BucketTable {
+    static final int SIZE = 1 << 11;
+    static final int MASK = SIZE - 1;
+
+    private FixedBitSet bitSet = new FixedBitSet(SIZE);
+    private boolean bitSetIsClean = true;
+    private final Bucket[] buckets = new Bucket[SIZE];
+    private Bucket first = null;        // head of valid list
+    private Bucket last = null;         // tail of valid list
+    // After collecting more documents, if there are more documents not collected.
+    boolean more = true;
+    int end = 0;
+
+    BucketTable() {
+      // Pre-fill to save the lazy init when collecting
+      // each sub:
+      for(int idx=0;idx<SIZE;idx++) {
+        buckets[idx] = new Bucket();
+      }
+    }
+
+    /**
+     * Force to collect more docs.
+     * It is not possible that more is true, while first is null.
+     * <code>more</code> will be true if more matching documents may remain.
+     */
+    void collectMoreForce() throws IOException {
+      do {
+        // If there are more docs not collected, but no doc is collected in this iteration,
+        // collect more again.
+        collectMore();
+      } while (more && first == null);
+    }
+
+    /**
+     * Collect more docs to bucket table. After calling this method,
+     * <code>more</code> will be true if more matching documents may remain.
+     */
+    private void collectMore() throws IOException {
+      if (!bitSetIsClean) {
+        // clean the bitset if it's not clean
+        bitSet.clear(0, SIZE);
+        bitSetIsClean = true;
+      }
+      first = last = null;
+      end += SIZE;
+      // Scan requiredDocs to build a required doc linked list on bucket table
+      int requiredDocID = requiredScorer.docID();
+      if (requiredDocID < 0) { // should call nextDoc() to get the first document
+        requiredDocID = requiredScorer.nextDoc();
+      }
+      while (requiredDocID < end) {
+        Bucket bucket = buckets[requiredDocID & MASK];
+        bucket.doc = requiredDocID;
+        bucket.coord = requiredNrMatch;
+        bucket.requiredScore = requiredScorer.score();
+        bucket.optionalScore = 0;
+        add(bucket);
+        requiredDocID = requiredScorer.nextDoc();
+      }
+      if (requiredDocID == DocIdSetIterator.NO_MORE_DOCS) {
+        more = false;
+      }
+      // Scan prohibitedDocs to remove docs from bucket table.
+      for (Scorer prohibitedScorer : prohibitedScorers) {
+        Bucket bucket = first;
+        while (bucket != null) {
+          int prohibitedDocID = prohibitedScorer.docID();
+          if (prohibitedDocID < bucket.doc) {
+            // According to the definition of .advance(),
+            // prohibitedDocID should be less then bucket.doc,
+            // before calling .advance(bucket.doc);
+
+            // Skip to bucket.doc, so that prohibitedDocID >= bucket.doc
+            prohibitedDocID = prohibitedScorer.advance(bucket.doc);
+          }
+
+          if (prohibitedDocID == DocIdSetIterator.NO_MORE_DOCS) {
+            break;
+
+          } else if (prohibitedDocID == bucket.doc) {
+            // remove the prohibited bucket.
+            Bucket oldBucket = bucket;
+            bucket = bucket.next;
+            remove(oldBucket);
+
+          } else { // prohibitedDocID > bucket.doc
+            if (prohibitedDocID >= end) {
+              bucket = null;
+            } else {
+              int bucketIndex = bitSet.nextSetBit(prohibitedDocID & MASK);
+              bucket = bucketIndex < 0 ? null : buckets[bucketIndex];
+            }
+          }
+        }
+      }
+
+      // Scan optionalDocs to add coord and score.
+      // TODO: use countLeft to judge whether a bucket should be removed.
+//      int countLeft = optionalScorers.size();
+      for (Scorer optionalScorer : optionalScorers) {
+//        countLeft --;
+        Bucket bucket = first;
+        while (bucket != null) {
+          int optionalDocID = optionalScorer.docID();
+          if (optionalDocID < bucket.doc) {
+            // According to the definition of .advance(),
+            // optionalDocID should be less then bucket.doc,
+            // before calling .advance(bucket.doc);
+
+            // Skip to bucket.doc, so that optionalDocID >= bucket.doc
+            optionalDocID = optionalScorer.advance(bucket.doc);
+          }
+
+          if (optionalDocID == DocIdSetIterator.NO_MORE_DOCS) {
+            break;
+
+          } else if (optionalDocID == bucket.doc) {
+            Bucket oldBucket = bucket;
+            bucket = bucket.next;
+            oldBucket.coord ++;
+//            oldBucket.score += optionalScorer.score();
+            oldBucket.optionalScore += optionalScorer.score();
+//            if (oldBucket.coord + countLeft < minNrShouldMatch) remove(oldBucket);
+
+          } else { // docID > bucket.doc
+            // current bucket advances to prohibtedDocID.
+            if (optionalDocID >= end) {
+              bucket = null;
+            } else {
+              int bucketIndex = bitSet.nextSetBit(optionalDocID & MASK);
+              bucket = bucketIndex < 0 ? null : buckets[bucketIndex];
+            }
+          }
+        }
+      }
+
+    }
+
+    /**
+     * Remove bucket from the double-linked list [first, last].
+     * @param bucket bucket to be removed from the double-linked list.
+     */
+    private void remove(Bucket bucket) {
+      bitSet.clear(bucket.doc & MASK);  // remove the bucket on bitset
+      if (first == bucket && last == bucket) {
+        first = last = null;
+        return;
+      }
+      if (first == bucket) {
+        first = first.next;
+        first.prev = null;
+        return;
+      }
+      if (last == bucket) {
+        last = last.prev;
+        last.next = null;
+        return;
+      }
+      Bucket prev = bucket.prev;
+      Bucket next = bucket.next;
+      prev.next = next;
+      next.prev = prev;
+    }
+
+    /**
+     * Add bucket to the tail of double-linked list [first, last].
+     * @param bucket bucket to be added to the double-linked list.
+     */
+    private void add(Bucket bucket) {
+      bitSet.set(bucket.doc & MASK);  // add the bucket to bitset
+      bitSetIsClean = false;          // the bitset is not clean now
+      if (first == null) {
+        first = last = bucket;
+        bucket.prev = null;
+        bucket.next = null;
+        return;
+      }
+      last.next = bucket;
+      bucket.prev = last;
+      bucket.next = null;
+      last = bucket;
+    }
+
+    void advance(int target) throws IOException {
+      end = target & ~MASK;
+      // advance requiredConjunctionScorer to target doc
+      if (requiredScorer.docID() < target) {
+        requiredScorer.advance(target);
+      }
+
+      // Scan prohibitedDocs to advance to target doc.
+      for (Scorer prohibitedScorer : prohibitedScorers) {
+        if (prohibitedScorer.docID() < target) {
+          prohibitedScorer.advance(target);
+        }
+      }
+
+      // Scan optionalDocs to advance to target doc.
+      for (Scorer optionalScorer : optionalScorers) {
+        if (optionalScorer.docID() < target) {
+          optionalScorer.advance(target);
+        }
+      }
+      current = null;
+      currentDoc = -1;
+    }
+  }
+
+  private final BucketTable bucketTable = new BucketTable();
+  private final float[] coordFactors;
+  // minNrShouldMatch only applies to SHOULD clauses
+  private Bucket current = null;
+  private int currentDoc = -1;
+
+  final private Scorer requiredScorer;
+  final int requiredNrMatch;
+  final private List<Scorer> optionalScorers;
+  final private List<Scorer> prohibitedScorers;
+  final private int minNrShouldMatch;
+
+  BooleanLinkedScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
+      List<Scorer> requiredScorers, List<Scorer> optionalScorers, List<Scorer> prohibitedScorers,
+      int maxCoord) throws IOException {
+    this(weight, disableCoord, minNrShouldMatch,
+        requiredScorers.isEmpty() ? null :
+          new ConjunctionScorer(weight, requiredScorers.toArray(new Scorer[requiredScorers.size()])),
+        requiredScorers.size(),
+        optionalScorers, prohibitedScorers, maxCoord);
+  }
+
+  BooleanLinkedScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
+      Scorer requiredScorer, int requiredNrMatch, List<Scorer> optionalScorers, List<Scorer> prohibitedScorers,
+      int maxCoord) throws IOException {
+    super(weight);
+    if (requiredNrMatch <= 0) {
+      throw new IllegalArgumentException("requriedScorers.size() must be > 0");
+    }
+    if (minNrShouldMatch < 0) {
+      throw new IllegalArgumentException("Minimum number of optional scorers should not be negative");
+    }
+    this.minNrShouldMatch = minNrShouldMatch;
+
+    this.requiredScorer = requiredScorer;
+    this.requiredNrMatch = requiredNrMatch;
+    this.optionalScorers = optionalScorers;
+    this.prohibitedScorers = prohibitedScorers;
+
+    coordFactors = new float[requiredNrMatch + optionalScorers.size() + 1];
+    for (int i = 0; i < coordFactors.length; i++) {
+      coordFactors[i] = disableCoord ? 1.0f : weight.coord(i, maxCoord);
+    }
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer buffer = new StringBuffer();
+    buffer.append("boolean(");
+    buffer.append("+");
+    buffer.append(requiredScorer.toString());
+    buffer.append(" ");
+    for (Scorer optionalScorer : optionalScorers) {
+      buffer.append(optionalScorer.toString());
+      buffer.append(" ");
+    }
+    for (Scorer prohibitedScorer : prohibitedScorers) {
+      buffer.append("-");
+      buffer.append(prohibitedScorer.toString());
+      buffer.append(" ");
+    }
+    // Delete the last whitespace
+    if (buffer.length() > "boolean(".length()) {
+      buffer.deleteCharAt(buffer.length() - 1);
+    }
+    buffer.append(")");
+    return buffer.toString();
+  }
+
+  @Override
+  public float score() throws IOException {
+    return current != null ?
+        ((float) current.requiredScore + (float) current.optionalScore) * coordFactors[current.coord] : Float.NaN;
+  }
+
+  @Override
+  public int freq() throws IOException {
+    return current != null ? current.coord : 0;
+  }
+
+  @Override
+  public int docID() {
+    return currentDoc;
+  }
+
+  @Override
+  public int nextDoc() throws IOException {
+    if (current != null) {
+      current = current.next;
+    }
+    if (bucketTable.more && current == null) {
+      bucketTable.collectMoreForce();
+      current = bucketTable.first;
+    }
+
+    while (current != null) {
+      if (current.coord - requiredNrMatch >= minNrShouldMatch) {
+        currentDoc = current.doc;
+        return currentDoc;
+
+      } else {
+        current = current.next;
+      }
+
+      if (bucketTable.more && current == null) {
+        bucketTable.collectMoreForce();
+        current = bucketTable.first;
+      }
+    }
+    return currentDoc = DocIdSetIterator.NO_MORE_DOCS;
+  }
+
+  @Override
+  public int advance(int target) throws IOException {
+    if (target < bucketTable.end) {
+      return slowAdvance(target);
+
+    } else {
+      bucketTable.advance(target);
+      return slowAdvance(target);
+    }
+  }
+
+  @Override
+  public long cost() {
+    return requiredScorer.cost();
+  }
+}
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 4d7635d..d9a708a 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java
@@ -20,6 +20,7 @@ package org.apache.lucene.search;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -42,8 +43,8 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
 
   /** Thrown when an attempt is made to add more than {@link
    * #getMaxClauseCount()} clauses. This typically happens if
-   * a PrefixQuery, FuzzyQuery, WildcardQuery, or TermRangeQuery 
-   * is expanded to many terms during search. 
+   * a PrefixQuery, FuzzyQuery, WildcardQuery, or TermRangeQuery
+   * is expanded to many terms during search.
    */
   public static class TooManyClauses extends RuntimeException {
     public TooManyClauses() {
@@ -58,7 +59,7 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
    */
   public static int getMaxClauseCount() { return maxClauseCount; }
 
-  /** 
+  /**
    * Set the maximum number of clauses permitted per BooleanQuery.
    * Default value is 1024.
    */
@@ -286,10 +287,10 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
                                "of optional clauses: " + minShouldMatch);
         return sumExpl;
       }
-      
+
       sumExpl.setMatch(0 < coord ? Boolean.TRUE : Boolean.FALSE);
       sumExpl.setValue(sum);
-      
+
       final float coordFactor = disableCoord ? 1.0f : coord(coord, maxCoord);
       if (coordFactor == 1.0f) {
         return sumExpl;                             // eliminate wrapper
@@ -308,12 +309,13 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
     public BulkScorer bulkScorer(AtomicReaderContext context, boolean scoreDocsInOrder,
                                  Bits acceptDocs) throws IOException {
 
-      if (scoreDocsInOrder || minNrShouldMatch > 1) {
+      if (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<Scorer> required = new ArrayList<Scorer>();
       List<BulkScorer> prohibited = new ArrayList<BulkScorer>();
       List<BulkScorer> optional = new ArrayList<BulkScorer>();
       Iterator<BooleanClause> cIter = clauses.iterator();
@@ -325,18 +327,123 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
             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);
+          // Get required scorer
+          Scorer requiredSubScorer = w.scorer(context, acceptDocs);
+          // if no doc matches required, then return null to say
+          // no doc matches this Boolean Query.
+          if (requiredSubScorer == null) {
+            return null;
+          }
+          required.add(requiredSubScorer);
+
         } else if (c.isProhibited()) {
           prohibited.add(subScorer);
+
         } else {
           optional.add(subScorer);
         }
       }
 
-      return new BooleanScorer(this, disableCoord, minNrShouldMatch, optional, prohibited, maxCoord);
+      if (required.isEmpty() && !scoreDocsInOrder) {
+        return new BooleanScorer(this, disableCoord, minNrShouldMatch, required, optional, prohibited, maxCoord);
+      }
+
+      int minShouldMatch = minNrShouldMatch;
+
+      float requiredCost = Float.MAX_VALUE;
+      float optionalCost = 0F;
+      float prohibitedCost = 0F;
+
+      List<Scorer> prohibitedScorers = new ArrayList<>();
+      List<Scorer> optionalScorers = new ArrayList<>();
+      cIter = clauses.iterator();
+      for (Weight w  : weights) {
+        BooleanClause c =  cIter.next();
+        Scorer subScorer = w.scorer(context, acceptDocs);
+        if (subScorer != null) {
+          if (c.isRequired()) {
+            requiredCost = Math.min(requiredCost, subScorer.cost());
+
+          } else if (c.isProhibited()) {
+            prohibitedScorers.add(subScorer);
+            prohibitedCost += subScorer.cost();
+
+          } else {
+            optionalScorers.add(subScorer);
+            optionalCost += subScorer.cost();
+          }
+        }
+      }
+      requiredCost *= Math.pow(0.97, required.size());
+      prohibitedCost /= prohibitedScorers.size();
+      optionalCost /= optionalScorers.size();
+
+      // scorer simplifications:
+      if (optionalScorers.size() == minShouldMatch) {
+        // any optional clauses are in fact required
+        required.addAll(optionalScorers);
+        optionalScorers.clear();
+        optional.clear();
+        minShouldMatch = 0;
+      }
+
+      if (required.isEmpty() && optionalScorers.isEmpty()) {
+        // no required and optional clauses.
+        return null;
+      } else if (optionalScorers.size() < minShouldMatch) {
+        // either >1 req scorer, or there are 0 req scorers and at least 1
+        // optional scorer. Therefore if there are not enough optional scorers
+        // no documents will be matched by the query
+        return null;
+      }
+
+      if (required.isEmpty()) {
+        // DAAT is the only choice
+        return new DefaultBulkScorer(
+            excl(opt(optionalScorers, minShouldMatch, disableCoord), prohibitedScorers));
+      }
+
+      Scorer req;
+      Class<?> scorerNotClass = scorerClassForNot(requiredCost, prohibitedScorers.size(), prohibitedCost);
+//      if (!optionalScorers.isEmpty() || !prohibitedScorers.isEmpty())
+//        scorerNotClass = BooleanArrayScorer.class;
+      if (scorerNotClass == BooleanArrayScorer.class) {
+        req = new BooleanArrayScorer(this, !optionalScorers.isEmpty() || disableCoord, 0,
+            required, Collections.<Scorer> emptyList(), prohibitedScorers, maxCoord);
+      } else if (scorerNotClass == BooleanLinkedScorer.class) {
+        req = new BooleanLinkedScorer(this, !optionalScorers.isEmpty() || disableCoord, 0,
+            required, Collections.<Scorer> emptyList(), prohibitedScorers, maxCoord);
+      } else {
+        req = excl(req(required, !optionalScorers.isEmpty() || disableCoord), prohibitedScorers);
+      }
+
+      // required is not empty
+      // pure conjunction
+      if (optionalScorers.isEmpty()) {
+        return new DefaultBulkScorer(req);
+      }
+
+      requiredCost *= Math.pow(0.97, prohibitedScorers.size());
+      // conjunction-disjunction mix:
+      // we create the required and optional pieces with coord disabled, and then
+      // combine the two: if minNrShouldMatch > 0, then its a conjunction: because the
+      // optional side must match. otherwise its required + optional, factoring the
+      // number of optional terms into the coord calculation
+      Scorer opt;
+      Class<?> scorerOrClass = scorerClassForOr(requiredCost, optionalScorers.size(), optionalCost);
+//      scorerOrClass = BooleanArrayScorer.class;
+      if (scorerOrClass == BooleanArrayScorer.class) {
+        return new DefaultBulkScorer(new BooleanArrayScorer(this, disableCoord, minShouldMatch,
+            req, required.size(), optionalScorers, Collections.<Scorer> emptyList(), maxCoord));
+      }
+      if (scorerOrClass == BooleanLinkedScorer.class) {
+        return new DefaultBulkScorer(new BooleanLinkedScorer(this, disableCoord, minShouldMatch,
+            req, required.size(), optionalScorers, Collections.<Scorer> emptyList(), maxCoord));
+      }
+      opt = opt(optionalScorers, minShouldMatch, true);
+
+      return new DefaultBulkScorer(
+          getMixScorer(required.size(), optionalScorers.size(), req, opt, minShouldMatch));
     }
 
     @Override
@@ -366,16 +473,16 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
           optional.add(subScorer);
         }
       }
-      
+
       // scorer simplifications:
-      
+
       if (optional.size() == minShouldMatch) {
         // any optional clauses are in fact required
         required.addAll(optional);
         optional.clear();
         minShouldMatch = 0;
       }
-      
+
       if (required.isEmpty() && optional.isEmpty()) {
         // no required and optional clauses.
         return null;
@@ -385,52 +492,116 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
         // no documents will be matched by the query
         return null;
       }
-      
-      // three cases: conjunction, disjunction, or mix
-      
+
+      // three cases: disjunction, conjunction, or mix
+
       // pure conjunction
       if (optional.isEmpty()) {
         return excl(req(required, disableCoord), prohibited);
       }
-      
+
       // pure disjunction
       if (required.isEmpty()) {
         return excl(opt(optional, minShouldMatch, disableCoord), prohibited);
       }
-      
+
       // conjunction-disjunction mix:
       // we create the required and optional pieces with coord disabled, and then
       // combine the two: if minNrShouldMatch > 0, then its a conjunction: because the
       // optional side must match. otherwise its required + optional, factoring the
       // number of optional terms into the coord calculation
-      
+
       Scorer req = excl(req(required, true), prohibited);
       Scorer opt = opt(optional, minShouldMatch, true);
 
+      return getMixScorer(required.size(), optional.size(), req, opt, minShouldMatch);
+    }
+
+    private Scorer getMixScorer(int requiredSize, int optionalSize,
+        Scorer req, Scorer opt, int minShouldMatch) {
       // TODO: clean this up: its horrible
       if (disableCoord) {
         if (minShouldMatch > 0) {
           return new ConjunctionScorer(this, new Scorer[] { req, opt }, 1F);
         } else {
-          return new ReqOptSumScorer(req, opt);          
+          return new ReqOptSumScorer(req, opt);
         }
-      } else if (optional.size() == 1) {
+      } else if (optionalSize == 1) {
         if (minShouldMatch > 0) {
-          return new ConjunctionScorer(this, new Scorer[] { req, opt }, coord(required.size()+1, maxCoord));
+          return new ConjunctionScorer(this, new Scorer[] { req, opt }, coord(requiredSize+1, maxCoord));
         } else {
-          float coordReq = coord(required.size(), maxCoord);
-          float coordBoth = coord(required.size() + 1, maxCoord);
+          float coordReq = coord(requiredSize, maxCoord);
+          float coordBoth = coord(requiredSize + 1, maxCoord);
           return new BooleanTopLevelScorers.ReqSingleOptScorer(req, opt, coordReq, coordBoth);
         }
       } else {
         if (minShouldMatch > 0) {
-          return new BooleanTopLevelScorers.CoordinatingConjunctionScorer(this, coords(), req, required.size(), opt);
+          return new BooleanTopLevelScorers.CoordinatingConjunctionScorer(this, coords(), req, requiredSize, opt);
         } else {
-          return new BooleanTopLevelScorers.ReqMultiOptScorer(req, opt, required.size(), coords()); 
+          return new BooleanTopLevelScorers.ReqMultiOptScorer(req, opt, requiredSize, coords());
         }
       }
     }
-    
+
+    private boolean isTons(int num) {
+      return num >= 10;
+    }
+
+    private boolean isSome(int num) {
+      return num >= 3 && !isTons(num);
+    }
+
+    private boolean isHigh(float cost) {
+      return cost >= 1e5f;
+    }
+
+    private boolean isLow(float cost) {
+      return !isHigh(cost);
+    }
+
+    private Class<?> scorerClassForNot(float requiredCost, int prohibitedSize, float prohibitedCost) {
+//      if (isTons(prohibitedSize)) {
+//        return BooleanLinkedScorer.class;
+//      }
+//      if (isSome(prohibitedSize) && isHigh(prohibitedCost)) {
+//        return BooleanArrayScorer.class;
+//      }
+      final float a = (float) Math.log(requiredCost);
+      final float b = (float) Math.log(prohibitedCost);
+      final float c = a * b;
+      final float d = c / (a + b);
+      final float A = a * -12.3435f + b * -11.0941f + c *  0.0232f + d *  47.2820f;
+      final float B = a *  21.6271f + b *  26.6788f + c * -0.1548f + d * -97.0084f;
+      if (A * prohibitedSize + B > 5) {
+        return BooleanArrayScorer.class;
+      }
+      return null;
+    }
+
+    private Class<?> scorerClassForOr(float requiredCost, int optionalSize, float optionalCost) {
+//      if (isTons(optionalSize)) { // Tons
+//        if (isHigh(requiredCost)) { // HighAnd
+//          return BooleanLinkedScorer.class;
+//
+//        } else { // LowAnd
+//          return BooleanArrayScorer.class;
+//        }
+//      }
+//      if (isSome(optionalSize) && isLow(requiredCost) && isHigh(optionalCost)) { // LowAnd5HighOr
+//        return BooleanArrayScorer.class;
+//      }
+      final float a = (float) Math.log(requiredCost);
+      final float b = (float) Math.log(optionalCost);
+      final float c = a * b;
+      final float d = c / (a + b);
+      final float A = a * -9.3337f + b * -8.6297f + c * 0.0146f + d * 36.6630f;
+      final float B = a * -7.8219f + b *  7.3330f + c * 0.1831f + d * -8.7804f;
+      if (A * optionalSize + B > 5) {
+        return BooleanArrayScorer.class;
+      }
+      return null;
+    }
+
     @Override
     public boolean scoresDocsOutOfOrder() {
       if (minNrShouldMatch > 1) {
@@ -446,15 +617,15 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
           optionalCount++;
         }
       }
-      
+
       if (optionalCount == minNrShouldMatch) {
         return false; // BS2 (in-order) will be used, as this means conjunction
       }
-      
+
       // scorer() will return an out-of-order scorer if requested.
       return true;
     }
-    
+
     private Scorer req(List<Scorer> required, boolean disableCoord) {
       if (required.size() == 1) {
         Scorer req = required.get(0);
@@ -464,12 +635,12 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
           return req;
         }
       } else {
-        return new ConjunctionScorer(this, 
+        return new ConjunctionScorer(this,
                                      required.toArray(new Scorer[required.size()]),
                                      disableCoord ? 1.0F : coord(required.size(), maxCoord));
       }
     }
-    
+
     private Scorer excl(Scorer main, List<Scorer> prohibited) throws IOException {
       if (prohibited.isEmpty()) {
         return main;
@@ -478,13 +649,13 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
       } else {
         float coords[] = new float[prohibited.size()+1];
         Arrays.fill(coords, 1F);
-        return new ReqExclScorer(main, 
-                                 new DisjunctionSumScorer(this, 
-                                                          prohibited.toArray(new Scorer[prohibited.size()]), 
+        return new ReqExclScorer(main,
+                                 new DisjunctionSumScorer(this,
+                                                          prohibited.toArray(new Scorer[prohibited.size()]),
                                                           coords));
       }
     }
-    
+
     private Scorer opt(List<Scorer> optional, int minShouldMatch, boolean disableCoord) throws IOException {
       if (optional.size() == 1) {
         Scorer opt = optional.get(0);
@@ -504,13 +675,13 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
         if (minShouldMatch > 1) {
           return new MinShouldMatchSumScorer(this, optional, minShouldMatch, coords);
         } else {
-          return new DisjunctionSumScorer(this, 
-                                          optional.toArray(new Scorer[optional.size()]), 
+          return new DisjunctionSumScorer(this,
+                                          optional.toArray(new Scorer[optional.size()]),
                                           coords);
         }
       }
     }
-    
+
     private float[] coords() {
       float[] coords = new float[maxCoord+1];
       coords[0] = 0F;
@@ -656,5 +827,5 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
     return Float.floatToIntBits(getBoost()) ^ clauses.hashCode()
       + getMinimumNumberShouldMatch() + (disableCoord ? 17:0);
   }
-  
+
 }
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 173bb44..3356406 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BooleanScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BooleanScorer.java
@@ -18,12 +18,8 @@ package org.apache.lucene.search;
  */
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
 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
@@ -60,69 +56,74 @@ import org.apache.lucene.search.BooleanQuery.BooleanWeight;
  * updates for the optional terms. */
 
 final class BooleanScorer extends BulkScorer {
-  
-  private static final class BooleanScorerCollector extends SimpleCollector {
+
+  private final class BooleanScorerCollector extends SimpleCollector {
     private BucketTable bucketTable;
     private int mask;
     private Scorer scorer;
-    
+
     public BooleanScorerCollector(int mask, BucketTable bucketTable) {
       this.mask = mask;
       this.bucketTable = bucketTable;
     }
-    
+
     @Override
     public void collect(final int doc) throws IOException {
       final BucketTable table = bucketTable;
       final int i = doc & BucketTable.MASK;
       final Bucket bucket = table.buckets[i];
-      
+
+      final int coord = (mask & REQUIRED_MASK) == REQUIRED_MASK ? requiredNrMatch : 1;
       if (bucket.doc != doc) {                    // invalid bucket
         bucket.doc = doc;                         // set doc
-        bucket.score = scorer.score();            // initialize score
+        bucket.requiredScore = 0;                 // initialize required score
+        bucket.optionalScore = 0;                 // initialize optional score
         bucket.bits = mask;                       // initialize mask
-        bucket.coord = 1;                         // initialize coord
-
+        bucket.coord = coord;                     // initialize coord
         bucket.next = table.first;                // push onto valid list
         table.first = bucket;
       } else {                                    // valid bucket
-        bucket.score += scorer.score();           // increment score
         bucket.bits |= mask;                      // add bits in mask
-        bucket.coord++;                           // increment coord
+        bucket.coord += coord;                    // increment coord
+      }
+
+      if ((mask & REQUIRED_MASK) == REQUIRED_MASK) { // Required doc
+        bucket.requiredScore += scorer.score();
+      } else if (mask == 0) { // Optional doc
+        bucket.optionalScore += scorer.score();
       }
     }
-    
+
     @Override
     public void setScorer(Scorer scorer) {
       this.scorer = scorer;
     }
-    
+
     @Override
     public boolean acceptsDocsOutOfOrder() {
       return true;
     }
-
   }
-  
+
   static final class Bucket {
     int doc = -1;            // tells if bucket is valid
-    double score;             // incremental score
-    // TODO: break out bool anyProhibited, int
-    // numRequiredMatched; then we can remove 32 limit on
-    // required clauses
+    // score is divided into RS and OS, so that its calculating order
+    // can be the same as the schedule of DAAT.
+    double requiredScore;    // incremental required score
+    double optionalScore;    // incremental optional score
     int bits;                // used for bool constraints
     int coord;               // count of terms in score
     Bucket next;             // next valid bucket
   }
-  
+
   /** A simple hash table of document scores within a range. */
-  static final class BucketTable {
+  final class BucketTable {
     public static final int SIZE = 1 << 11;
     public static final int MASK = SIZE - 1;
 
     final Bucket[] buckets = new Bucket[SIZE];
     Bucket first = null;                          // head of valid list
-  
+
     public BucketTable() {
       // Pre-fill to save the lazy init when collecting
       // each sub:
@@ -140,8 +141,7 @@ final class BooleanScorer extends BulkScorer {
 
   static final class SubScorer {
     public BulkScorer scorer;
-    // TODO: re-enable this if BQ ever sends us required clauses
-    //public boolean required = false;
+    public boolean required = false;
     public boolean prohibited;
     public LeafCollector collector;
     public SubScorer next;
@@ -149,48 +149,65 @@ final class BooleanScorer extends BulkScorer {
 
     public SubScorer(BulkScorer scorer, boolean required, boolean prohibited,
         LeafCollector 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.required = required;
       this.prohibited = prohibited;
       this.collector = collector;
       this.next = next;
     }
   }
-  
+
   private SubScorer scorers = null;
   private BucketTable bucketTable = new BucketTable();
   private final float[] coordFactors;
-  // TODO: re-enable this if BQ ever sends us required clauses
-  //private int requiredMask = 0;
+  // minNrShouldMatch only applies to SHOULD clauses
   private final int minNrShouldMatch;
   private int end;
   private Bucket current;
   // Any time a prohibited clause matches we set bit 0:
   private static final int PROHIBITED_MASK = 1;
+  // Any time a required clause matches we set bit 1:
+  private static final int REQUIRED_MASK = 2;
+  // requiredNrMatch applies to MUST clauses
+  private final int requiredNrMatch;
+
+//  private final Weight weight;
 
-  private final Weight weight;
+  BooleanScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
+      List<Scorer> requiredScorers, List<BulkScorer> optionalScorers, List<BulkScorer> prohibitedScorers,
+      int maxCoord) throws IOException {
+    this(weight, disableCoord, minNrShouldMatch,
+        requiredScorers.isEmpty() ? null :
+          new ConjunctionScorer(weight, requiredScorers.toArray(new Scorer[requiredScorers.size()])),
+        requiredScorers.size(),
+        optionalScorers, prohibitedScorers, maxCoord);
+  }
 
   BooleanScorer(BooleanWeight weight, boolean disableCoord, int minNrShouldMatch,
-      List<BulkScorer> optionalScorers, List<BulkScorer> prohibitedScorers, int maxCoord) throws IOException {
+      Scorer requiredScorer, int requiredNrMatch, List<BulkScorer> optionalScorers, List<BulkScorer> prohibitedScorers,
+      int maxCoord) throws IOException {
+
     this.minNrShouldMatch = minNrShouldMatch;
-    this.weight = weight;
+//    this.weight = weight;
+
+    this.requiredNrMatch = requiredNrMatch;
+    if (requiredNrMatch > 0) {
+      BulkScorer required = new Weight.DefaultBulkScorer(requiredScorer);
+      scorers = new SubScorer(required, true, false, bucketTable.newCollector(REQUIRED_MASK), scorers);
+    }
 
     for (BulkScorer scorer : optionalScorers) {
       scorers = new SubScorer(scorer, false, false, bucketTable.newCollector(0), scorers);
     }
-    
+
     for (BulkScorer scorer : prohibitedScorers) {
       scorers = new SubScorer(scorer, false, true, bucketTable.newCollector(PROHIBITED_MASK), scorers);
     }
 
-    coordFactors = new float[optionalScorers.size() + 1];
+    coordFactors = new float[requiredNrMatch + optionalScorers.size() + 1];
     for (int i = 0; i < coordFactors.length; i++) {
-      coordFactors[i] = disableCoord ? 1.0f : weight.coord(i, maxCoord); 
+      coordFactors[i] = disableCoord ? 1.0f : weight.coord(i, maxCoord);
     }
   }
 
@@ -205,16 +222,13 @@ final class BooleanScorer extends BulkScorer {
     collector.setScorer(fs);
     do {
       bucketTable.first = null;
-      
-      while (current != null) {         // more queued 
+
+      while (current != null) {         // more queued
 
         // check prohibited & required
-        if ((current.bits & PROHIBITED_MASK) == 0) {
+        if ((current.bits & PROHIBITED_MASK) == 0 &&
+            (requiredNrMatch == 0 || (current.bits & REQUIRED_MASK) == REQUIRED_MASK)) {
 
-          // TODO: re-enable this if BQ ever sends us required
-          // clauses
-          //&& (current.bits & requiredMask) == requiredMask) {
-          
           // NOTE: Lucene always passes max =
           // Integer.MAX_VALUE today, because we never embed
           // a BooleanScorer inside another (even though
@@ -228,18 +242,20 @@ final class BooleanScorer extends BulkScorer {
             bucketTable.first = tmp;
             continue;
           }
-          
-          if (current.coord >= minNrShouldMatch) {
-            fs.score = (float) (current.score * coordFactors[current.coord]);
+
+          if (current.coord - requiredNrMatch >= minNrShouldMatch) {
+            // Cast required score and optional score to float,
+            // in order to make the calculating procedure is the same as DAAT.
+            fs.score = (float) (((float) current.requiredScore + (float) current.optionalScore) * coordFactors[current.coord]);
             fs.doc = current.doc;
             fs.freq = current.coord;
             collector.collect(current.doc);
           }
         }
-        
+
         current = current.next;         // pop the queue
       }
-      
+
       if (bucketTable.first != null){
         current = bucketTable.first;
         bucketTable.first = current.next;
@@ -256,7 +272,7 @@ final class BooleanScorer extends BulkScorer {
         }
       }
       current = bucketTable.first;
-      
+
     } while (current != null || more);
 
     return false;
@@ -270,6 +286,10 @@ final class BooleanScorer extends BulkScorer {
       buffer.append(sub.scorer.toString());
       buffer.append(" ");
     }
+    // Delete the last whitespace
+    if (buffer.length() > "boolean(".length()) {
+      buffer.deleteCharAt(buffer.length() - 1);
+    }
     buffer.append(")");
     return buffer.toString();
   }
diff --git a/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java b/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
index 3e81187..e303ed8 100644
--- a/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java
@@ -102,12 +102,11 @@ class ConjunctionScorer extends Scorer {
 
   @Override
   public float score() throws IOException {
-    // TODO: sum into a double and cast to float if we ever send required clauses to BS1
-    float sum = 0.0f;
+    double sum = 0.0f;
     for (DocsAndFreqs docs : docsAndFreqs) {
       sum += docs.scorer.score();
     }
-    return sum * coord;
+    return (float) (sum * coord);
   }
   
   @Override
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 358a513..69d71af 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestBooleanScorer.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBooleanScorer.java
@@ -96,7 +96,7 @@ public class TestBooleanScorer extends LuceneTestCase {
       }
     }};
     
-    BooleanScorer bs = new BooleanScorer(weight, false, 1, Arrays.asList(scorers), Collections.<BulkScorer>emptyList(), scorers.length);
+    BooleanScorer bs = new BooleanScorer(weight, false, 1, Collections.<Scorer>emptyList(), Arrays.asList(scorers), Collections.<BulkScorer>emptyList(), scorers.length);
 
     final List<Integer> hits = new ArrayList<>();
     bs.score(new SimpleCollector() {
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBooleanUnevenly.java b/lucene/core/src/test/org/apache/lucene/search/TestBooleanUnevenly.java
new file mode 100644
index 0000000..fce73f3
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBooleanUnevenly.java
@@ -0,0 +1,132 @@
+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 org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * BooleanQuery.scorer should be tested, when hit documents
+ * are very unevenly distributed.
+ */
+public class TestBooleanUnevenly extends LuceneTestCase {
+  private static IndexSearcher searcher;
+  private static IndexReader reader;
+
+  public static final String field = "field";
+  private static Directory directory;
+
+  private static int count1 = 0;
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    directory = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), directory, new MockAnalyzer(random()));
+    Document doc;
+    for (int i=0;i<2;i++) {
+      for (int j=0;j<2048;j++) {
+        doc = new Document();
+        doc.add(newTextField(field, "1", Field.Store.NO));
+        count1 ++;
+        w.addDocument(doc);
+      }
+      for (int j=0;j<2048;j++) {
+        doc = new Document();
+        doc.add(newTextField(field, "2", Field.Store.NO));
+        w.addDocument(doc);
+      }
+      doc = new Document();
+      doc.add(newTextField(field, "1", Field.Store.NO));
+      count1 ++;
+      w.addDocument(doc);
+      for (int j=0;j<2048;j++) {
+        doc = new Document();
+        doc.add(newTextField(field, "2", Field.Store.NO));
+        w.addDocument(doc);
+      }
+    }
+    reader = w.getReader();
+    searcher = newSearcher(reader);
+    w.shutdown();
+  }
+
+  @AfterClass
+  public static void afterClass() throws Exception {
+    reader.close();
+    directory.close();
+    searcher = null;
+    reader = null;
+    directory = null;
+  }
+
+  @Test
+  public void testQueries01() throws Exception {
+    BooleanQuery query = new BooleanQuery();
+    query.add(new TermQuery(new Term(field, "1")), BooleanClause.Occur.MUST);
+    query.add(new TermQuery(new Term(field, "1")), BooleanClause.Occur.SHOULD);
+    query.add(new TermQuery(new Term(field, "2")), BooleanClause.Occur.SHOULD);
+
+    TopScoreDocCollector collector = TopScoreDocCollector.create(1000, false);
+    searcher.search(query, null, collector);
+    TopDocs tops1 = collector.topDocs();
+    ScoreDoc[] hits1 = tops1.scoreDocs;
+    int hitsNum1 = tops1.totalHits;
+
+    collector = TopScoreDocCollector.create(1000, true);
+    searcher.search(query, null, collector);
+    TopDocs tops2 = collector.topDocs();
+    ScoreDoc[] hits2 = tops2.scoreDocs;
+    int hitsNum2 = tops2.totalHits;
+
+    assertEquals(hitsNum1, count1);
+    assertEquals(hitsNum2, count1);
+    CheckHits.checkEqual(query, hits1, hits2);
+  }
+
+  @Test
+  public void testQueries02() throws Exception {
+    BooleanQuery query = new BooleanQuery();
+    query.add(new TermQuery(new Term(field, "1")), BooleanClause.Occur.SHOULD);
+    query.add(new TermQuery(new Term(field, "1")), BooleanClause.Occur.SHOULD);
+
+    TopScoreDocCollector collector = TopScoreDocCollector.create(1000, false);
+    searcher.search(query, null, collector);
+    TopDocs tops1 = collector.topDocs();
+    ScoreDoc[] hits1 = tops1.scoreDocs;
+    int hitsNum1 = tops1.totalHits;
+
+    collector = TopScoreDocCollector.create(1000, true);
+    searcher.search(query, null, collector);
+    TopDocs tops2 = collector.topDocs();
+    ScoreDoc[] hits2 = tops2.scoreDocs;
+    int hitsNum2 = tops2.totalHits;
+
+    assertEquals(hitsNum1, count1);
+    assertEquals(hitsNum2, count1);
+    CheckHits.checkEqual(query, hits1, hits2);
+  }
+}
