Index: src/java/org/apache/lucene/search/BooleanQuery.java
===================================================================
--- src/java/org/apache/lucene/search/BooleanQuery.java	(revision 415858)
+++ src/java/org/apache/lucene/search/BooleanQuery.java	(working copy)
@@ -263,7 +263,7 @@
       throws IOException {
       final int minShouldMatch =
         BooleanQuery.this.getMinimumNumberShouldMatch();
-      Explanation sumExpl = new Explanation();
+      ComplexExplanation sumExpl = new ComplexExplanation();
       sumExpl.setDescription("sum of:");
       int coord = 0;
       int maxCoord = 0;
@@ -275,14 +275,14 @@
         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 {
             Explanation r =
-              new Explanation(0.0f, "match on prohibited clause");
+              new Explanation(0.0f, "match on prohibited clause (" + c.getQuery().toString() + ")");
             r.addDetail(e);
             sumExpl.addDetail(r);
             fail = true;
@@ -290,36 +290,39 @@
           if (c.getOccur().equals(Occur.SHOULD))
             shouldMatchCount++;
         } else if (c.isRequired()) {
-          Explanation r = new Explanation(0.0f, "no match on required clause");
+          Explanation r = new Explanation(0.0f, "no match on required clause (" + c.getQuery().toString() + ")");
           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(0 < coord ? Boolean.TRUE : Boolean.FALSE);
       sumExpl.setValue(sum);
       
       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:");
+        ComplexExplanation result = new ComplexExplanation(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 415858)
+++ src/java/org/apache/lucene/search/Explanation.java	(working copy)
@@ -31,6 +31,20 @@
     this.description = description;
   }
 
+  /**
+   * Indicates wether or not this Explanation models a good match.
+   *
+   * <p>
+   * By default, an Explanation represents a "match" if the value is positive.
+   * </p>
+   * @see #getValue
+   */
+  public boolean isMatch() {
+    return (0.0f < getValue());
+  }
+
+
+  
   /** The value assigned to this explanation node. */
   public float getValue() { return value; }
   /** Sets the value assigned to this explanation node. */
@@ -61,7 +75,7 @@
   public String toString() {
     return toString(0);
   }
-  private String toString(int depth) {
+  protected String toString(int depth) {
     StringBuffer buffer = new StringBuffer();
     for (int i = 0; i < depth; i++) {
       buffer.append("  ");
Index: src/java/org/apache/lucene/search/TermQuery.java
===================================================================
--- src/java/org/apache/lucene/search/TermQuery.java	(revision 415858)
+++ src/java/org/apache/lucene/search/TermQuery.java	(working copy)
@@ -72,7 +72,7 @@
     public Explanation explain(IndexReader reader, int doc)
       throws IOException {
 
-      Explanation result = new Explanation();
+      ComplexExplanation result = new ComplexExplanation();
       result.setDescription("weight("+getQuery()+" in "+doc+"), product of:");
 
       Explanation idfExpl =
@@ -98,7 +98,7 @@
 
       // explain field weight
       String field = term.field();
-      Explanation fieldExpl = new Explanation();
+      ComplexExplanation fieldExpl = new ComplexExplanation();
       fieldExpl.setDescription("fieldWeight("+term+" in "+doc+
                                "), product of:");
 
@@ -113,13 +113,15 @@
       fieldNormExpl.setValue(fieldNorm);
       fieldNormExpl.setDescription("fieldNorm(field="+field+", doc="+doc+")");
       fieldExpl.addDetail(fieldNormExpl);
-
+      
+      fieldExpl.setMatch(Boolean.valueOf(tfExpl.isMatch()));
       fieldExpl.setValue(tfExpl.getValue() *
                          idfExpl.getValue() *
                          fieldNormExpl.getValue());
 
       result.addDetail(fieldExpl);
-
+      result.setMatch(fieldExpl.getMatch());
+      
       // combine them
       result.setValue(queryExpl.getValue() * fieldExpl.getValue());
 
Index: src/java/org/apache/lucene/search/ComplexExplanation.java
===================================================================
--- src/java/org/apache/lucene/search/ComplexExplanation.java	(revision 0)
+++ src/java/org/apache/lucene/search/ComplexExplanation.java	(revision 0)
@@ -0,0 +1,121 @@
+package org.apache.lucene.search;
+
+/**
+ * Copyright 2004 The Apache Software Foundation
+ *
+ * Licensed 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.ArrayList;
+
+/** Expert: Describes the score computation for document and query, andcan distinguish a match independent of a positive value. */
+public class ComplexExplanation extends Explanation {
+  private Boolean match;
+  
+  public ComplexExplanation() {
+    super();
+  }
+
+  public ComplexExplanation(boolean match, float value, String description) {
+    // NOTE: use of "boolean" instead of "Boolean" in params is concious
+    // choice to force clients to be specific.
+    super(value, description);
+    this.match = Boolean.valueOf(match);
+  }
+
+  /**
+   * 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
+   */
+  public void setMatch(Boolean match) { this.match = match; }
+  /**
+   * Indicates wether or not this Explanation models a good match.
+   *
+   * <p>
+   * If the match statis is explicitly set (ie: not null) this method
+   * uses it; otherwise it defers to the superclass.
+   * </p>
+   * @see #getMatch
+   */
+  public boolean isMatch() {
+    Boolean m = getMatch();
+    return (null != m ? m.booleanValue() : super.isMatch());
+  }
+
+  protected String toString(int depth) {
+    // :TODO: it would be nice if there was an easier way to inherit partial functionality from the superclass
+    
+    if (null == getMatch())
+      return super.toString(depth);
+
+    
+    StringBuffer buffer = new StringBuffer();
+    for (int i = 0; i < depth; i++) {
+      buffer.append("  ");
+    }
+    buffer.append(getValue());
+    buffer.append(" = ");
+    buffer.append(isMatch() ? "(MATCH) " : "(NON-MATCH) ");
+    buffer.append(getDescription());
+    buffer.append("\n");
+
+    Explanation[] details = getDetails();
+    if (details != null) {
+      for (int i = 0 ; i < details.length; i++) {
+        buffer.append(details[i].toString(depth+1));
+      }
+    }
+
+    return buffer.toString();
+
+  }
+
+
+  /** Render an explanation as HTML. */
+  public String toHtml() {
+    // :TODO: it would be nice if there was an easier way to inherit partial functionality from the superclass
+    
+    if (null == getMatch())
+      return super.toHtml();
+    
+    
+    StringBuffer buffer = new StringBuffer();
+    buffer.append("<ul>\n");
+
+    buffer.append("<li>");
+    if (null != getMatch())
+      buffer.append(isMatch() ? "(MATCH) " : "(NON-MATCH) ");
+    
+    buffer.append(getValue());
+    buffer.append(" = ");
+    buffer.append(getDescription());
+    buffer.append("<br />\n");
+
+    Explanation[] details = getDetails();
+    if (details != null) {
+      for (int i = 0 ; i < details.length; i++) {
+        buffer.append(details[i].toHtml());
+      }
+    }
+
+    buffer.append("</li>\n");
+    buffer.append("</ul>\n");
+
+    return buffer.toString();
+  }
+}
