Index: lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java	(revision 1363128)
+++ lucene/core/src/java/org/apache/lucene/search/BooleanQuery.java	(working copy)
@@ -173,24 +173,18 @@
     protected ArrayList<Weight> weights;
     protected int maxCoord;  // num optional + num required
     private final boolean disableCoord;
-    private final boolean termConjunction;
 
     public BooleanWeight(IndexSearcher searcher, boolean disableCoord)
       throws IOException {
       this.similarity = searcher.getSimilarity();
       this.disableCoord = disableCoord;
       weights = new ArrayList<Weight>(clauses.size());
-      boolean termConjunction = clauses.isEmpty() || minNrShouldMatch != 0 ? false : true;
       for (int i = 0 ; i < clauses.size(); i++) {
         BooleanClause c = clauses.get(i);
         Weight w = c.getQuery().createWeight(searcher);
-        if (!(c.isRequired() && (w instanceof TermWeight))) {
-          termConjunction = false;
-        }
         weights.add(w);
         if (!c.isProhibited()) maxCoord++;
       }
-      this.termConjunction = termConjunction;
     }
 
     @Override
@@ -305,14 +299,11 @@
     public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
         boolean topScorer, Bits acceptDocs)
         throws IOException {
-      if (termConjunction) {
-        // specialized scorer for term conjunctions
-        return createConjunctionTermScorer(context, acceptDocs);
-      }
       List<Scorer> required = new ArrayList<Scorer>();
       List<Scorer> prohibited = new ArrayList<Scorer>();
       List<Scorer> optional = new ArrayList<Scorer>();
       Iterator<BooleanClause> cIter = clauses.iterator();
+      boolean termConjunction = true;
       for (Weight w  : weights) {
         BooleanClause c =  cIter.next();
         Scorer subScorer = w.scorer(context, true, false, acceptDocs);
@@ -322,18 +313,18 @@
           }
         } else if (c.isRequired()) {
           required.add(subScorer);
+          termConjunction &= w instanceof TermWeight;
         } else if (c.isProhibited()) {
           prohibited.add(subScorer);
+          termConjunction = false; // TODO: this is dumb, but lets generalize the whole opto first
         } else {
           optional.add(subScorer);
+          termConjunction &= w instanceof TermWeight;
         }
       }
       
-      // Check if we can return a BooleanScorer
-      if (!scoreDocsInOrder && topScorer && required.size() == 0) {
-        return new BooleanScorer(this, disableCoord, minNrShouldMatch, optional, prohibited, maxCoord);
-      }
-      
+      termConjunction &= optional.size() == minNrShouldMatch;
+
       if (required.size() == 0 && optional.size() == 0) {
         // no required and optional clauses.
         return null;
@@ -344,42 +335,50 @@
         return null;
       }
       
+      // check if we can return a ConjunctionTermScorer
+      // this is possible when all mandatory clauses are terms,
+      // and all optional clauses are also terms and minNrShouldMatch == optional.size()
+      if (termConjunction) {
+        List<Scorer> subs = new ArrayList<Scorer>(required.size() + optional.size());
+        subs.addAll(required);
+        subs.addAll(optional);
+        DocsAndFreqs[] docsAndFreqs = new DocsAndFreqs[subs.size()];
+        for (int i = 0; i < docsAndFreqs.length; i++) {
+          Scorer scorer = subs.get(i);
+          if (scorer instanceof TermScorer) {
+            docsAndFreqs[i] = new DocsAndFreqs((TermScorer) scorer);
+          } else {
+            docsAndFreqs[i] = new DocsAndFreqs((MatchOnlyTermScorer) scorer);
+          }
+        }
+        return new ConjunctionTermScorer(this, disableCoord ? 1.0f : coord(
+            docsAndFreqs.length, docsAndFreqs.length), docsAndFreqs);
+      }
+
+      // Check if we can return a BooleanScorer
+      if (!scoreDocsInOrder && topScorer && required.size() == 0 && optional.size() != minNrShouldMatch) {
+        return new BooleanScorer(this, disableCoord, minNrShouldMatch, optional, prohibited, maxCoord);
+      }
+      
       // Return a BooleanScorer2
       return new BooleanScorer2(this, disableCoord, minNrShouldMatch, required, prohibited, optional, maxCoord);
     }
-
-    private Scorer createConjunctionTermScorer(AtomicReaderContext context, Bits acceptDocs)
-        throws IOException {
-
-      // TODO: fix scorer API to specify "needsScores" up
-      // front, so we can do match-only if caller doesn't
-      // needs scores
-
-      final DocsAndFreqs[] docsAndFreqs = new DocsAndFreqs[weights.size()];
-      for (int i = 0; i < docsAndFreqs.length; i++) {
-        final TermWeight weight = (TermWeight) weights.get(i);
-        final Scorer scorer = weight.scorer(context, true, false, acceptDocs);
-        if (scorer == null) {
-          return null;
-        }
-        if (scorer instanceof TermScorer) {
-          docsAndFreqs[i] = new DocsAndFreqs((TermScorer) scorer);
-        } else {
-          docsAndFreqs[i] = new DocsAndFreqs((MatchOnlyTermScorer) scorer);
-        }
-      }
-      return new ConjunctionTermScorer(this, disableCoord ? 1.0f : coord(
-          docsAndFreqs.length, docsAndFreqs.length), docsAndFreqs);
-    }
     
     @Override
     public boolean scoresDocsOutOfOrder() {
+      int optionalCount = 0;
       for (BooleanClause c : clauses) {
         if (c.isRequired()) {
           return false; // BS2 (in-order) will be used by scorer()
+        } else if (!c.isProhibited()) {
+          optionalCount++;
         }
       }
       
+      if (optionalCount == minNrShouldMatch) {
+        return false; // BS2 (in-order) will be used, as this means we have mandatory clauses
+      }
+      
       // scorer() will return an out-of-order scorer if requested.
       return true;
     }
