Index: src/java/org/apache/lucene/search/BooleanQuery.java
===================================================================
--- src/java/org/apache/lucene/search/BooleanQuery.java	(revision 414976)
+++ src/java/org/apache/lucene/search/BooleanQuery.java	(working copy)
@@ -18,6 +18,7 @@
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.util.ToStringUtils;
+import org.apache.lucene.search.BooleanClause.Occur;
 
 import java.io.IOException;
 import java.util.Iterator;
@@ -188,8 +189,11 @@
       for (int i = 0 ; i < weights.size(); i++) {
         BooleanClause c = (BooleanClause)clauses.elementAt(i);
         Weight w = (Weight)weights.elementAt(i);
+        // call sumOfSquaredWeights for all clauses in case of side effects
+        float s = w.sumOfSquaredWeights();         // sum sub weights
         if (!c.isProhibited())
-          sum += w.sumOfSquaredWeights();         // sum sub weights
+          // only add to sum for non-prohibited clauses
+          sum += s;
       }
 
       sum *= getBoost() * getBoost();             // boost each sub-weight
@@ -203,8 +207,8 @@
       for (int i = 0 ; i < weights.size(); i++) {
         BooleanClause c = (BooleanClause)clauses.elementAt(i);
         Weight w = (Weight)weights.elementAt(i);
-        if (!c.isProhibited())
-          w.normalize(norm);
+        // normalize all clauses, (even if prohibited in case of side affects)
+        w.normalize(norm);
       }
     }
 
@@ -257,43 +261,67 @@
 
     public Explanation explain(IndexReader reader, int doc)
       throws IOException {
+      final int minShouldMatch =
+        BooleanQuery.this.getMinimumNumberShouldMatch();
       Explanation sumExpl = new Explanation();
       sumExpl.setDescription("sum of:");
       int coord = 0;
       int maxCoord = 0;
       float sum = 0.0f;
+      boolean fail = false;
+      int shouldMatchCount = 0;
       for (int i = 0 ; i < weights.size(); i++) {
         BooleanClause c = (BooleanClause)clauses.elementAt(i);
         Weight w = (Weight)weights.elementAt(i);
         Explanation e = w.explain(reader, doc);
         if (!c.isProhibited()) maxCoord++;
-        if (e.getValue() > 0) {
+        if (e.isMatch()) {
           if (!c.isProhibited()) {
             sumExpl.addDetail(e);
             sum += e.getValue();
             coord++;
           } else {
-            return new Explanation(0.0f, "match prohibited");
+            Explanation r =
+              new Explanation(false, 0.0f, "match on prohibited clause");
+            r.addDetail(e);
+            sumExpl.addDetail(r);
+            fail = true;
           }
+          if (c.getOccur().equals(Occur.SHOULD))
+            shouldMatchCount++;
         } else if (c.isRequired()) {
-          return new Explanation(0.0f, "match required");
+          Explanation r = new Explanation(false, 0.0f, "no match on required clause");
+          r.addDetail(e);
+          sumExpl.addDetail(r);
+          fail = true;
         }
       }
+      if (fail) {
+        sumExpl.setMatch(Boolean.FALSE);
+        sumExpl.setValue(0.0f);
+        sumExpl.setDescription
+          ("Failure to meet condition(s) of required/prohibited clause(s)");
+        return sumExpl;
+      } else if (shouldMatchCount < minShouldMatch) {
+        sumExpl.setMatch(Boolean.FALSE);
+        sumExpl.setValue(0.0f);
+        sumExpl.setDescription("Failure to match minimum number "+
+                               "of optional clauses: " + minShouldMatch);
+        return sumExpl;
+      }
+      
+      sumExpl.setMatch(Boolean.TRUE);
       sumExpl.setValue(sum);
-
-      if (coord == 1)                               // only one clause matched
-        sumExpl = sumExpl.getDetails()[0];          // eliminate wrapper
-
+      
       float coordFactor = similarity.coord(coord, maxCoord);
       if (coordFactor == 1.0f)                      // coord is no-op
         return sumExpl;                             // eliminate wrapper
       else {
-        Explanation result = new Explanation();
-        result.setDescription("product of:");
+        Explanation result =
+          new Explanation(sumExpl.isMatch(),sum*coordFactor,"product of:");
         result.addDetail(sumExpl);
         result.addDetail(new Explanation(coordFactor,
                                          "coord("+coord+"/"+maxCoord+")"));
-        result.setValue(sum*coordFactor);
         return result;
       }
     }
Index: src/java/org/apache/lucene/search/Explanation.java
===================================================================
--- src/java/org/apache/lucene/search/Explanation.java	(revision 414976)
+++ src/java/org/apache/lucene/search/Explanation.java	(working copy)
@@ -20,17 +20,77 @@
 
 /** Expert: Describes the score computation for document and query. */
 public class Explanation implements java.io.Serializable {
-  private float value;                            // the value of this node
-  private String description;                     // what it represents
-  private ArrayList details;                      // sub-explanations
+  /**
+   * Indicates wether the Explanation models a document which matches a query.
+   * Null indicates that the match status is unknown, and may be infered
+   * from the Value.
+   * @see #value
+   */ 
+  private Boolean match;
+  /**
+   * The value of this Explanation.
+   * Legacy code may use the value of "0.0f" to indicate a
+   * non-matching Explanation.
+   * @see #match
+   */
+  private float value;
+  /** What this node represents */
+  private String description;
+  /** Optional sub-explanations */
+  private ArrayList details;
 
   public Explanation() {}
 
+  /**
+   * Constructs and Explanation.
+   * @param match Does this explanation model a match of a document and a query
+   * @param value Numeric Value for this explanation
+   * @param description Summary of what this Explanation models
+   */
+  public Explanation(boolean match, float value, String description) {
+    // NOTE: use of "boolean" instead of "Boolean" in params is concious
+    // choice to force clients to be specific.
+    this.match = Boolean.valueOf(match);
+    this.value = value;
+    this.description = description;
+  }
+     
+  /**
+   * :TODO: comment out this constructor, change code that breaks, re-add it
+   * @deprecated
+   */
   public Explanation(float value, String description) {
     this.value = value;
     this.description = description;
   }
+  
+  /**
+   * The match status of this explanation node.
+   * @return May be null if match status is unknown
+   */
+  public Boolean getMatch() { return match; }
+  /**
+   * Sets the match status assigned to this explanation node.
+   * @param match May be null if match status is unknown
+   * @see #isMatch
+   */
+  public void setMatch(Boolean match) { this.match = match; }
+  /**
+   * The infered match status of this explanation node.
+   * If the match status is unknown, this method attempts to "guess"
+   * based on the value
+   *
+   * @see #getMatch
+   * @see #getValue
+   */
+  public boolean isMatch() {
+    Boolean m = getMatch();
+    return (null == m)
+      ? 0.0f != getValue()
+      : m.booleanValue();
+  }
 
+
   /** The value assigned to this explanation node. */
   public float getValue() { return value; }
   /** Sets the value assigned to this explanation node. */
@@ -57,9 +117,20 @@
     details.add(detail);
   }
 
+  /**
+   * Reusable Helper Function
+   *
+   */
+  private String getMatchString() {
+    return (isMatch() ? "(MATCH" : "(NON-MATCH")
+      + (null == getMatch() ? "?)" : ")");
+
+  }
+  
   /** Render an explanation as text. */
   public String toString() {
-    return toString(0);
+    // :TODO: should we include the getMatchString() here?
+    return getMatchString() + " " + toString(0);
   }
   private String toString(int depth) {
     StringBuffer buffer = new StringBuffer();
@@ -68,6 +139,10 @@
     }
     buffer.append(getValue());
     buffer.append(" = ");
+    // :TODO: this should really die .. but it's helpfull in debugging
+    buffer.append(getMatchString());
+    buffer.append(" ");
+    // :TODO: END
     buffer.append(getDescription());
     buffer.append("\n");
 
Index: src/java/org/apache/lucene/search/TermQuery.java
===================================================================
--- src/java/org/apache/lucene/search/TermQuery.java	(revision 414976)
+++ src/java/org/apache/lucene/search/TermQuery.java	(working copy)
@@ -117,9 +117,11 @@
       fieldExpl.setValue(tfExpl.getValue() *
                          idfExpl.getValue() *
                          fieldNormExpl.getValue());
-
+      fieldExpl.setMatch(Boolean.valueOf(tfExpl.isMatch()));
+      
       result.addDetail(fieldExpl);
-
+      result.setMatch(fieldExpl.getMatch());
+      
       // combine them
       result.setValue(queryExpl.getValue() * fieldExpl.getValue());
 
