Index: src/java/org/apache/lucene/search/Explanation.java
===================================================================
--- src/java/org/apache/lucene/search/Explanation.java	(revision 807187)
+++ src/java/org/apache/lucene/search/Explanation.java	(working copy)
@@ -17,6 +17,7 @@
  * limitations under the License.
  */
 
+import java.io.Serializable;
 import java.util.ArrayList;
 
 /** Expert: Describes the score computation for document and query. */
@@ -124,4 +125,25 @@
 
     return buffer.toString();
   }
+  
+  /**
+   * Small Util class used to pass both an idf factor as well as an
+   * explanation for that factor.
+   * 
+   * This class will likely be held on a {@link Weight}, so you should
+   * be aware if storing any large or un-serializable fields.
+   *
+   */
+  public static abstract class IDFExplain implements Serializable {
+    /**
+     * @return the idf factor
+     */
+    public abstract float getIdf();
+    /**
+     * This should be calculated lazily if possible.
+     * 
+     * @return the explanation for the idf factor.
+     */
+    public abstract String explain();
+  }
 }
Index: src/java/org/apache/lucene/search/PhraseQuery.java
===================================================================
--- src/java/org/apache/lucene/search/PhraseQuery.java	(revision 807187)
+++ src/java/org/apache/lucene/search/PhraseQuery.java	(working copy)
@@ -18,12 +18,15 @@
  */
 
 import java.io.IOException;
+import java.util.Collection;
 import java.util.Set;
 import java.util.ArrayList;
 
+import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermPositions;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Explanation.IDFExplain;
 import org.apache.lucene.util.ToStringUtils;
 
 /** A Query that matches documents containing a particular sequence of terms.
@@ -112,14 +115,44 @@
     private float idf;
     private float queryNorm;
     private float queryWeight;
+    private IDFExplain idfExp;
 
     public PhraseWeight(Searcher searcher)
       throws IOException {
       this.similarity = getSimilarity(searcher);
 
-      idf = similarity.idf(terms, searcher);
+      // TODO: remove in 3.0
+      boolean overridesOldIdf = isMethodOverridden(this.similarity.getClass(), "idf", IDF_METHOD_PARAMS);
+      if(!overridesOldIdf) {
+        idfExp = similarity.idfExplain(terms, searcher);
+        idf = idfExp.getIdf();
+      } else {
+        idf = similarity.idf(terms, searcher);
+        idfExp = new IDFExplain() {
+          //@Override
+          public float getIdf() {
+            return idf;
+          }
+          //@Override
+          public String explain() {
+            return "Unknown Explanation";
+          }
+        };
+      }
     }
 
+    // TODO: remove in 3.0
+    private final Class[] IDF_METHOD_PARAMS = new Class[]{Collection.class, Searcher.class};
+    // TODO: remove in 3.0
+    private boolean isMethodOverridden(Class clazz, String name, Class[] params) {
+      try {
+        return clazz.getMethod(name, params).getDeclaringClass() != Similarity.class;
+      } catch (NoSuchMethodException e) {
+        // should not happen
+        throw new RuntimeException(e);
+      }
+    }
+    
     public String toString() { return "weight(" + PhraseQuery.this + ")"; }
 
     public Query getQuery() { return PhraseQuery.this; }
@@ -158,33 +191,29 @@
 
     }
 
-    public Explanation explain(IndexReader reader, int doc)
-      throws IOException {
+    public Explanation explain(IndexReader reader, int doc) throws IOException {
 
       Explanation result = new Explanation();
-      result.setDescription("weight("+getQuery()+" in "+doc+"), product of:");
+      result.setDescription("weight(" + getQuery() + " in " + doc
+          + "), product of:");
 
       StringBuffer docFreqs = new StringBuffer();
       StringBuffer query = new StringBuffer();
       query.append('\"');
+      docFreqs.append(idfExp.explain());
       for (int i = 0; i < terms.size(); i++) {
         if (i != 0) {
-          docFreqs.append(" ");
           query.append(" ");
         }
 
-        Term term = (Term)terms.get(i);
-
-        docFreqs.append(term.text());
-        docFreqs.append("=");
-        docFreqs.append(reader.docFreq(term));
+        Term term = (Term) terms.get(i);
 
         query.append(term.text());
       }
       query.append('\"');
 
-      Explanation idfExpl =
-        new Explanation(idf, "idf(" + field + ": " + docFreqs + ")");
+      Explanation idfExpl = new Explanation(idf, "idf(" + field + ":"
+          + docFreqs + ")");
 
       // explain query weight
       Explanation queryExpl = new Explanation();
@@ -195,19 +224,18 @@
         queryExpl.addDetail(boostExpl);
       queryExpl.addDetail(idfExpl);
 
-      Explanation queryNormExpl = new Explanation(queryNorm,"queryNorm");
+      Explanation queryNormExpl = new Explanation(queryNorm, "queryNorm");
       queryExpl.addDetail(queryNormExpl);
 
-      queryExpl.setValue(boostExpl.getValue() *
-                         idfExpl.getValue() *
-                         queryNormExpl.getValue());
+      queryExpl.setValue(boostExpl.getValue() * idfExpl.getValue()
+          * queryNormExpl.getValue());
 
       result.addDetail(queryExpl);
 
       // explain field weight
       Explanation fieldExpl = new Explanation();
-      fieldExpl.setDescription("fieldWeight("+field+":"+query+" in "+doc+
-                               "), product of:");
+      fieldExpl.setDescription("fieldWeight(" + field + ":" + query + " in "
+          + doc + "), product of:");
 
       Scorer scorer = scorer(reader, true, false);
       if (scorer == null) {
@@ -219,15 +247,15 @@
 
       Explanation fieldNormExpl = new Explanation();
       byte[] fieldNorms = reader.norms(field);
-      float fieldNorm =
-        fieldNorms!=null ? Similarity.decodeNorm(fieldNorms[doc]) : 1.0f;
+      float fieldNorm = fieldNorms != null ? Similarity
+          .decodeNorm(fieldNorms[doc]) : 1.0f;
       fieldNormExpl.setValue(fieldNorm);
-      fieldNormExpl.setDescription("fieldNorm(field="+field+", doc="+doc+")");
+      fieldNormExpl.setDescription("fieldNorm(field=" + field + ", doc=" + doc
+          + ")");
       fieldExpl.addDetail(fieldNormExpl);
 
-      fieldExpl.setValue(tfExpl.getValue() *
-                         idfExpl.getValue() *
-                         fieldNormExpl.getValue());
+      fieldExpl.setValue(tfExpl.getValue() * idfExpl.getValue()
+          * fieldNormExpl.getValue());
 
       result.addDetail(fieldExpl);
 
Index: src/java/org/apache/lucene/search/Similarity.java
===================================================================
--- src/java/org/apache/lucene/search/Similarity.java	(revision 807187)
+++ src/java/org/apache/lucene/search/Similarity.java	(working copy)
@@ -19,6 +19,7 @@
 
 import org.apache.lucene.index.FieldInvertState;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.Explanation.IDFExplain;
 import org.apache.lucene.util.SmallFloat;
 
 import java.io.IOException;
@@ -478,10 +479,49 @@
    * @param term the term in question
    * @param searcher the document collection being searched
    * @return a score factor for the term
+   * @deprecated see {@link #idfExplain(Term, Searcher)}
    */
   public float idf(Term term, Searcher searcher) throws IOException {
     return idf(searcher.docFreq(term), searcher.maxDoc());
   }
+  
+  /**
+   * Computes a score factor for a simple term and returns an explantion
+   * for that score factor.
+   * 
+   * <p>
+   * The default implementation uses:
+   * 
+   * <pre>
+   * idf(searcher.docFreq(term), searcher.maxDoc());
+   * </pre>
+   * 
+   * Note that {@link Searcher#maxDoc()} is used instead of
+   * {@link org.apache.lucene.index.IndexReader#numDocs()} because it is
+   * proportional to {@link Searcher#docFreq(Term)} , i.e., when one is
+   * inaccurate, so is the other, and in the same direction.
+   * 
+   * @param term the term in question
+   * @param searcher the document collection being searched
+   * @return an IDFExplain object that includes both a score factor for the term
+   *         and an explaination for the term.
+   * @throws IOException
+   */
+  public IDFExplain idfExplain(Term term, Searcher searcher) throws IOException {
+    final int df = searcher.docFreq(term);
+    final int max = searcher.maxDoc();
+
+    return new IDFExplain() {
+        //@Override
+        public String explain() {
+          return "idf(docFreq=" + df +
+          ", maxDocs=" + max + ")";
+        }
+        //@Override
+        public float getIdf() {
+          return idf(df, max);
+        }};
+   }
 
   /** Computes a score factor for a phrase.
    *
@@ -490,7 +530,8 @@
    *
    * @param terms the terms in the phrase
    * @param searcher the document collection being searched
-   * @return a score factor for the phrase
+   * @return  
+   * @deprecated see {@link #idfExplain(Collection, Searcher)}
    */
   public float idf(Collection terms, Searcher searcher) throws IOException {
     float idf = 0.0f;
@@ -500,6 +541,47 @@
     }
     return idf;
   }
+  
+  /**
+   * Computes a score factor for a phrase.
+   * 
+   * <p>
+   * The default implementation sums the idf factor for
+   * each term in the phrase.
+   * 
+   * @param terms the terms in the phrase
+   * @param searcher the document collection being searched
+   * @return an IDFExplain object that includes both a score factor for the
+   *         phrase and an explaination for each term.
+   * @throws IOException
+   */
+  public IDFExplain idfExplain(Collection terms, Searcher searcher) throws IOException {
+
+    final int max = searcher.maxDoc();
+    float idf = 0.0f;
+    final StringBuffer exp = new StringBuffer();
+    Iterator i = terms.iterator();
+    while (i.hasNext()) {
+      Term term = (Term)i.next();
+      final int df = searcher.docFreq(term);
+      idf += idf(df, max);
+      exp.append(" ");
+      exp.append(term.text());
+      exp.append("=");
+      exp.append(df);
+    }
+    final float fIdf = idf;
+    return new IDFExplain() {
+      //@Override
+      public float getIdf() {
+        return fIdf;
+      }
+      //@Override
+      public String explain() {
+        return exp.toString();
+      }
+    };
+  }
 
   /** Computes a score factor based on a term's document frequency (the number
    * of documents which contain the term).  This value is multiplied by the
Index: src/java/org/apache/lucene/search/TermQuery.java
===================================================================
--- src/java/org/apache/lucene/search/TermQuery.java	(revision 807187)
+++ src/java/org/apache/lucene/search/TermQuery.java	(working copy)
@@ -20,9 +20,12 @@
 import java.io.IOException;
 import java.util.Set;
 
+import org.apache.lucene.analysis.Token;
+import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Explanation.IDFExplain;
 import org.apache.lucene.util.ToStringUtils;
 
 /** A Query that matches documents containing a term.
@@ -37,11 +40,40 @@
     private float idf;
     private float queryNorm;
     private float queryWeight;
+    private IDFExplain idfExp;
 
     public TermWeight(Searcher searcher)
       throws IOException {
       this.similarity = getSimilarity(searcher);
-      idf = similarity.idf(term, searcher); // compute idf
+      // TODO: remove in 3.0
+      boolean overridesOldIdf = isMethodOverridden(this.similarity.getClass(), "idf", IDF_METHOD_PARAMS);
+      if(!overridesOldIdf) {
+        idfExp = similarity.idfExplain(term, searcher);
+        idf = idfExp.getIdf();
+      } else {
+        idf = similarity.idf(term, searcher);
+        idfExp = new IDFExplain() {
+          //@Override
+          public float getIdf() {
+            return idf;
+          }
+          //@Override
+          public String explain() {
+            return "Unknown Explanation";
+          }
+        };
+      }
+    }
+    // TODO: remove in 3.0
+    private final Class[] IDF_METHOD_PARAMS = new Class[]{Term.class, Searcher.class};
+    // TODO: remove in 3.0
+    private boolean isMethodOverridden(Class clazz, String name, Class[] params) {
+      try {
+        return clazz.getMethod(name, params).getDeclaringClass() != Similarity.class;
+      } catch (NoSuchMethodException e) {
+        // should not happen
+        throw new RuntimeException(e);
+      }
     }
 
     public String toString() { return "weight(" + TermQuery.this + ")"; }
@@ -75,8 +107,7 @@
       ComplexExplanation result = new ComplexExplanation();
       result.setDescription("weight("+getQuery()+" in "+doc+"), product of:");
 
-      Explanation expl = new Explanation(idf, "idf(docFreq=" + reader.docFreq(term) +
-            ", maxDocs=" + reader.maxDoc() + ")");
+      Explanation expl = new Explanation(idf, idfExp.explain());
 
       // explain query weight
       Explanation queryExpl = new Explanation();
Index: src/java/org/apache/lucene/search/spans/SpanWeight.java
===================================================================
--- src/java/org/apache/lucene/search/spans/SpanWeight.java	(revision 807187)
+++ src/java/org/apache/lucene/search/spans/SpanWeight.java	(working copy)
@@ -17,13 +17,14 @@
  * limitations under the License.
  */
 
+import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.Term;
 import org.apache.lucene.search.*;
+import org.apache.lucene.search.Explanation.IDFExplain;
 
 import java.io.IOException;
+import java.util.Collection;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Set;
 
 /**
@@ -38,6 +39,7 @@
 
   protected Set terms;
   protected SpanQuery query;
+  private IDFExplain idfExp;
 
   public SpanWeight(SpanQuery query, Searcher searcher)
     throws IOException {
@@ -46,7 +48,36 @@
     terms=new HashSet();
     query.extractTerms(terms);
 
-    idf = this.query.getSimilarity(searcher).idf(terms, searcher);
+    // TODO: remove in 3.0
+    boolean overridesOldIdf = isMethodOverridden(this.similarity.getClass(), "idf", IDF_METHOD_PARAMS);
+    if(!overridesOldIdf) {
+      idfExp = similarity.idfExplain(terms, searcher);
+      idf = idfExp.getIdf();
+    } else {
+      idf = similarity.idf(terms, searcher);
+      idfExp = new IDFExplain() {
+        //@Override
+        public float getIdf() {
+          return idf;
+        }
+        //@Override
+        public String explain() {
+          return "Unknown Explanation";
+        }
+      };
+    }
+  }
+  
+  // TODO: remove in 3.0
+  private static final Class[] IDF_METHOD_PARAMS = new Class[]{Collection.class, Searcher.class};
+  // TODO: remove in 3.0
+  private boolean isMethodOverridden(Class clazz, String name, Class[] params) {
+    try {
+      return clazz.getMethod(name, params).getDeclaringClass() != Similarity.class;
+    } catch (NoSuchMethodException e) {
+      // should not happen
+      throw new RuntimeException(e);
+    }
   }
 
   public Query getQuery() { return query; }
@@ -75,21 +106,8 @@
     result.setDescription("weight("+getQuery()+" in "+doc+"), product of:");
     String field = ((SpanQuery)getQuery()).getField();
 
-    StringBuffer docFreqs = new StringBuffer();
-    Iterator i = terms.iterator();
-    while (i.hasNext()) {
-      Term term = (Term)i.next();
-      docFreqs.append(term.text());
-      docFreqs.append("=");
-      docFreqs.append(reader.docFreq(term));
-
-      if (i.hasNext()) {
-        docFreqs.append(" ");
-      }
-    }
-
     Explanation idfExpl =
-      new Explanation(idf, "idf(" + field + ": " + docFreqs + ")");
+      new Explanation(idf, "idf(" + field + ": " + idfExp.explain() + ")");
 
     // explain query weight
     Explanation queryExpl = new Explanation();
Index: src/test/org/apache/lucene/search/TestSimpleExplanations.java
===================================================================
--- src/test/org/apache/lucene/search/TestSimpleExplanations.java	(revision 807187)
+++ src/test/org/apache/lucene/search/TestSimpleExplanations.java	(working copy)
@@ -1,5 +1,15 @@
 package org.apache.lucene.search;
 
+import java.io.IOException;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.MockRAMDirectory;
+
 /**
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -291,4 +301,46 @@
   }
   
   
+  public void testTermQueryMultiSearcherExplain() throws Exception {
+    // creating two directories for indices
+    Directory indexStoreA = new MockRAMDirectory();
+    Directory indexStoreB = new MockRAMDirectory();
+
+    Document lDoc = new Document();
+    lDoc.add(new Field("handle", "1", Field.Store.YES, Field.Index.NOT_ANALYZED));
+    Document lDoc2 = new Document();
+    lDoc2.add(new Field("handle", "1", Field.Store.YES, Field.Index.NOT_ANALYZED));
+    Document lDoc3 = new Document();
+    lDoc3.add(new Field("handle", "1", Field.Store.YES, Field.Index.NOT_ANALYZED));
+
+    IndexWriter writerA = new IndexWriter(indexStoreA, new StandardAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED);
+    IndexWriter writerB = new IndexWriter(indexStoreB, new StandardAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED);
+
+    writerA.addDocument(lDoc);
+    writerA.addDocument(lDoc2);
+    writerA.optimize();
+    writerA.close();
+
+    writerB.addDocument(lDoc3);
+    writerB.close();
+
+    QueryParser parser = new QueryParser("fulltext", new StandardAnalyzer());
+    Query query = parser.parse("handle:1");
+
+    Searcher[] searchers = new Searcher[2];
+    searchers[0] = new IndexSearcher(indexStoreB);
+    searchers[1] = new IndexSearcher(indexStoreA);
+    Searcher mSearcher = new MultiSearcher(searchers);
+    ScoreDoc[] hits = mSearcher.search(query, null, 1000).scoreDocs;
+
+    assertEquals(3, hits.length);
+
+    Explanation explain = mSearcher.explain(query, hits[0].doc);
+    String exp = explain.toString(0);
+    assertTrue(exp.contains("maxDocs=3"));
+    assertTrue(exp.contains("docFreq=3"));
+    mSearcher.close();
+  }
+  
+  
 }

