You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-commits@lucene.apache.org by mi...@apache.org on 2009/04/13 20:33:58 UTC

svn commit: r764551 [1/3] - in /lucene/java/trunk: ./ contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ contrib/miscellaneous/src/test/org/apache/lucene/index/ contrib/miscellaneous/src/test/org/apache/lucene/misc/ contrib/spatial/sr...

Author: mikemccand
Date: Mon Apr 13 18:33:56 2009
New Revision: 764551

URL: http://svn.apache.org/viewvc?rev=764551&view=rev
Log:
LUCENE-1575: switch to new Collector API

Added:
    lucene/java/trunk/src/java/org/apache/lucene/search/Collector.java
    lucene/java/trunk/src/java/org/apache/lucene/search/HitCollectorWrapper.java
    lucene/java/trunk/src/java/org/apache/lucene/search/PositiveScoresOnlyCollector.java
    lucene/java/trunk/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TimeLimitingCollector.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TopDocsCollector.java
    lucene/java/trunk/src/test/org/apache/lucene/search/JustCompileSearch.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestPositiveScoresOnlyCollector.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestScoreCachingWrappingScorer.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestTimeLimitingCollector.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestTopDocsCollector.java
    lucene/java/trunk/src/test/org/apache/lucene/search/function/JustCompileSearchSpans.java
    lucene/java/trunk/src/test/org/apache/lucene/search/spans/JustCompileSearchSpans.java
Removed:
    lucene/java/trunk/src/java/org/apache/lucene/search/MultiReaderHitCollector.java
Modified:
    lucene/java/trunk/CHANGES.txt
    lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java
    lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java
    lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/index/TestFieldNormModifier.java
    lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/misc/TestLengthNormModifier.java
    lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java
    lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer.java
    lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer2.java
    lucene/java/trunk/src/java/org/apache/lucene/search/DisjunctionSumScorer.java
    lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparator.java
    lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparatorSource.java
    lucene/java/trunk/src/java/org/apache/lucene/search/FieldValueHitQueue.java
    lucene/java/trunk/src/java/org/apache/lucene/search/HitCollector.java
    lucene/java/trunk/src/java/org/apache/lucene/search/IndexSearcher.java
    lucene/java/trunk/src/java/org/apache/lucene/search/MultiSearcher.java
    lucene/java/trunk/src/java/org/apache/lucene/search/ParallelMultiSearcher.java
    lucene/java/trunk/src/java/org/apache/lucene/search/QueryWrapperFilter.java
    lucene/java/trunk/src/java/org/apache/lucene/search/RemoteSearchable.java
    lucene/java/trunk/src/java/org/apache/lucene/search/Scorer.java
    lucene/java/trunk/src/java/org/apache/lucene/search/Searchable.java
    lucene/java/trunk/src/java/org/apache/lucene/search/Searcher.java
    lucene/java/trunk/src/java/org/apache/lucene/search/SortField.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TermScorer.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TimeLimitedCollector.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TopDocs.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TopFieldCollector.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TopFieldDocCollector.java
    lucene/java/trunk/src/java/org/apache/lucene/search/TopScoreDocCollector.java
    lucene/java/trunk/src/test/org/apache/lucene/index/TestIndexReader.java
    lucene/java/trunk/src/test/org/apache/lucene/index/TestOmitTf.java
    lucene/java/trunk/src/test/org/apache/lucene/search/CheckHits.java
    lucene/java/trunk/src/test/org/apache/lucene/search/QueryUtils.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestDocBoost.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestMultiTermConstantScore.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestScorerPerf.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestSetNorm.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestSimilarity.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestSort.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestTermScorer.java
    lucene/java/trunk/src/test/org/apache/lucene/search/TestTimeLimitedCollector.java

Modified: lucene/java/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/java/trunk/CHANGES.txt?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/CHANGES.txt (original)
+++ lucene/java/trunk/CHANGES.txt Mon Apr 13 18:33:56 2009
@@ -3,6 +3,32 @@
 
 ======================= Trunk (not yet released) =======================
 
+Changes in backwards compatibility policy
+
+ 1. LUCENE-1575: Searchable.search(Weight, Filter, int, Sort)
+    currently tracks document scores (including maxScore), and sets
+    the score in each returned FieldDoc.  However, in 3.0 it will stop
+    tracking document scores. If document scores tracking is still
+    needed, you can use Searchable.search(Weight, Filter, Collector)
+    and pass in a TopFieldCollector instance, using the following code
+    sample:
+ 
+    <code>
+      TopFieldCollector tfc = TopFieldCollector.create(sort, numHits, fillFields, 
+                                                       true /* trackDocScores */,
+                                                       true /* trackMaxScore */);
+      searcher.search(weight, filter, tfc);
+      TopDocs results = tfc.topDocs();
+    </code>
+
+    Also, the method search(Weight, Filter, Collector) was added to
+    the Searchable interface and the Searcher abstract class, to
+    replace the deprecated HitCollector versions.  If you either
+    implement Searchable or extend Searcher, you should change you
+    code to implement this method.  If you already extend
+    IndexSearcher, no further changes are needed to use Collector.
+    (Shai Erera via Mike McCandless)
+
 Changes in runtime behavior
 
  1. LUCENE-1424: QueryParser now by default uses constant score query
@@ -10,6 +36,19 @@
     already does so for RangeQuery, as well).  Call
     setConstantScoreRewrite(false) to revert to BooleanQuery rewriting
     method.  (Mark Miller via Mike McCandless)
+    
+ 2. LUCENE-1575: As of 2.9, the core collectors as well as
+    IndexSearcher's search methods that return top N results, no
+    longer filter out zero scoring documents. If you rely on this
+    functionaliy you can use PositiveScoresOnlyCollector like this:
+
+    <code>
+      TopDocsCollector tdc = new TopScoreDocCollector(10);
+      Collector c = new PositiveScoresOnlyCollector(tdc);
+      searcher.search(query, c);
+      TopDocs hits = tdc.topDocs();
+      ...
+    </code>
 
 API Changes
 
@@ -69,6 +108,14 @@
 12. LUCENE-1500: Added new InvalidTokenOffsetsException to Highlighter methods
     to denote issues when offsets in TokenStream tokens exceed the length of the
     provided text.  (Mark Harwood)
+    
+13. LUCENE-1575: HitCollector is now deprecated in favor of a new
+    Collector abstract class. For easy migration, people can use
+    HitCollectorWrapper which translates (wraps) HitCollector into
+    Collector. Note that this class is also deprecated and will be
+    removed when HitCollector is removed.  Also TimeLimitedCollector
+    is deprecated in favor of the new TimeLimitingCollector which
+    extends Collector.  (Shai Erera via Mike McCandless)
 
 Bug fixes
 
@@ -258,6 +305,12 @@
     those segments that did not change, and also speeds up searches
     that sort by relevance or by field values.  (Mark Miller, Mike
     McCandless)
+    
+ 7. LUCENE-1575: The new Collector class decouples collect() from
+    score computation.  Collector.setScorer is called to establish the
+    current Scorer in-use per segment.  Collectors that require the
+    score should then call Scorer.score() per hit inside
+    collect(). (Shai Erera via Mike McCandless)
 
 Documentation
 

Modified: lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java (original)
+++ lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java Mon Apr 13 18:33:56 2009
@@ -32,6 +32,7 @@
 import org.apache.lucene.document.Fieldable;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.TopFieldCollector;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
@@ -62,7 +63,6 @@
   public ReadTask(PerfRunData runData) {
     super(runData);
   }
-
   public int doLogic() throws Exception {
     int res = 0;
     boolean closeReader = false;
@@ -102,7 +102,10 @@
       final int numHits = numHits();
       if (numHits > 0) {
         if (sort != null) {
-          hits = searcher.search(q, null, numHits, sort);
+          TopFieldCollector collector = TopFieldCollector.create(sort, numHits,
+              true, withScore(), withMaxScore());
+          searcher.search(q, collector);
+          hits = collector.topDocs();
         } else {
           hits = searcher.search(q, numHits);
         }
@@ -180,6 +183,18 @@
    */
   public abstract boolean withTraverse();
 
+  /** Whether scores should be computed (only useful with
+   *  field sort) */
+  public boolean withScore() {
+    return true;
+  }
+
+  /** Whether maxScores should be computed (only useful with
+   *  field sort) */
+  public boolean withMaxScore() {
+    return true;
+  }
+
   /**
    * Specify the number of hits to traverse.  Tasks should override this if they want to restrict the number
    * of hits that are traversed when {@link #withTraverse()} is true. Must be greater than 0.

Modified: lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java (original)
+++ lucene/java/trunk/contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java Mon Apr 13 18:33:56 2009
@@ -27,6 +27,8 @@
  */
 public class SearchWithSortTask extends ReadTask {
 
+  private boolean doScore = true;
+  private boolean doMaxScore = true;
   private Sort sort;
 
   public SearchWithSortTask(PerfRunData runData) {
@@ -34,7 +36,12 @@
   }
 
   /**
-   * SortFields: field:type,field:type
+   * SortFields: field:type,field:type[,noscore][,nomaxscore]
+   *
+   * If noscore is present, then we turn off score tracking
+   * in {@link org.apache.lucene.search.TopFieldCollector}.
+   * If nomaxscore is present, then we turn off maxScore tracking
+   * in {@link org.apache.lucene.search.TopFieldCollector}.
    * 
    * name,byline:int,subject:auto
    * 
@@ -43,11 +50,18 @@
     super.setParams(sortField);
     String[] fields = sortField.split(",");
     SortField[] sortFields = new SortField[fields.length];
+    int upto = 0;
     for (int i = 0; i < fields.length; i++) {
       String field = fields[i];
       SortField sortField0;
       if (field.equals("doc")) {
         sortField0 = SortField.FIELD_DOC;
+      } else if (field.equals("noscore")) {
+        doScore = false;
+        continue;
+      } else if (field.equals("nomaxscore")) {
+        doMaxScore = false;
+        continue;
       } else {
         int index = field.lastIndexOf(":");
         String fieldName;
@@ -62,7 +76,13 @@
         int type = getType(typeString);
         sortField0 = new SortField(fieldName, type);
       }
-      sortFields[i] = sortField0;
+      sortFields[upto++] = sortField0;
+    }
+
+    if (upto < sortFields.length) {
+      SortField[] newSortFields = new SortField[upto];
+      System.arraycopy(sortFields, 0, newSortFields, 0, upto);
+      sortFields = newSortFields;
     }
     this.sort = new Sort(sortFields);
   }
@@ -107,6 +127,14 @@
     return false;
   }
 
+  public boolean withScore() {
+    return doScore;
+  }
+
+  public boolean withMaxScore() {
+    return doMaxScore;
+  }
+  
   public Sort getSort() {
     if (sort == null) {
       throw new IllegalStateException("No sort field was set");

Modified: lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/index/TestFieldNormModifier.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/index/TestFieldNormModifier.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/index/TestFieldNormModifier.java (original)
+++ lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/index/TestFieldNormModifier.java Mon Apr 13 18:33:56 2009
@@ -22,16 +22,18 @@
 
 import junit.framework.TestCase;
 
+import org.apache.lucene.analysis.SimpleAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.DefaultSimilarity;
 import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MultiReaderHitCollector;
+import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.Similarity;
-import org.apache.lucene.search.DefaultSimilarity;
 import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.store.Directory;
-import org.apache.lucene.analysis.SimpleAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
+import org.apache.lucene.store.RAMDirectory;
 
 /**
  * Tests changing of field norms with a custom similarity and with fake norms.
@@ -52,12 +54,12 @@
   /** inverts the normal notion of lengthNorm */
   public static Similarity s = new DefaultSimilarity() {
     public float lengthNorm(String fieldName, int numTokens) {
-      return (float)numTokens;
+      return numTokens;
     }
   };
   
   public void setUp() throws Exception {
-    IndexWriter writer = new IndexWriter(store, new SimpleAnalyzer(), true);
+    IndexWriter writer = new IndexWriter(store, new SimpleAnalyzer(), true, MaxFieldLength.UNLIMITED);
     
     for (int i = 0; i < NUM_DOCS; i++) {
       Document d = new Document();
@@ -123,14 +125,19 @@
     float lastScore = 0.0f;
     
     // default similarity should put docs with shorter length first
-    searcher.search(new TermQuery(new Term("field", "word")), new MultiReaderHitCollector() {
-      private int docBase = -1;
-      public final void collect(int doc, float score) {
-        scores[doc + docBase] = score;
+    searcher.search(new TermQuery(new Term("field", "word")), new Collector() {
+      private int docBase = 0;
+      private Scorer scorer;
+      
+      public final void collect(int doc) throws IOException {
+        scores[doc + docBase] = scorer.score();
       }
       public void setNextReader(IndexReader reader, int docBase) {
         this.docBase = docBase;
       }
+      public void setScorer(Scorer scorer) throws IOException {
+        this.scorer = scorer;
+      }
     });
     searcher.close();
     
@@ -147,14 +154,18 @@
     
     // new norm (with default similarity) should put longer docs first
     searcher = new IndexSearcher(store);
-    searcher.search(new TermQuery(new Term("field", "word")),  new MultiReaderHitCollector() {
-      private int docBase = -1;
-      public final void collect(int doc, float score) {
-        scores[doc + docBase] = score;
+    searcher.search(new TermQuery(new Term("field", "word")),  new Collector() {
+      private int docBase = 0;
+      private Scorer scorer;
+      public final void collect(int doc) throws IOException {
+        scores[doc + docBase] = scorer.score();
       }
       public void setNextReader(IndexReader reader, int docBase) {
         this.docBase = docBase;
       }
+      public void setScorer(Scorer scorer) throws IOException {
+        this.scorer = scorer;
+      }
     });
     searcher.close();
     
@@ -188,15 +199,18 @@
     float lastScore = 0.0f;
     
     // default similarity should return the same score for all documents for this query
-    searcher.search(new TermQuery(new Term("untokfield", "20061212")), new MultiReaderHitCollector() {
-      private int docBase = -1;
-      private int lastMax; 
-      public final void collect(int doc, float score) {
-        scores[doc + docBase] = score;
+    searcher.search(new TermQuery(new Term("untokfield", "20061212")), new Collector() {
+      private int docBase = 0;
+      private Scorer scorer;
+      public final void collect(int doc) throws IOException {
+        scores[doc + docBase] = scorer.score();
       }
       public void setNextReader(IndexReader reader, int docBase) {
         this.docBase = docBase;
       }
+      public void setScorer(Scorer scorer) throws IOException {
+        this.scorer = scorer;
+      }
     });
     searcher.close();
     

Modified: lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/misc/TestLengthNormModifier.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/misc/TestLengthNormModifier.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/misc/TestLengthNormModifier.java (original)
+++ lucene/java/trunk/contrib/miscellaneous/src/test/org/apache/lucene/misc/TestLengthNormModifier.java Mon Apr 13 18:33:56 2009
@@ -17,21 +17,26 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+
 import junit.framework.TestCase;
 
-import org.apache.lucene.index.Term;
-import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.analysis.SimpleAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.FieldNormModifier;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.DefaultSimilarity;
 import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MultiReaderHitCollector;
+import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.Similarity;
-import org.apache.lucene.search.DefaultSimilarity;
 import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.store.Directory;
-import org.apache.lucene.analysis.SimpleAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
+import org.apache.lucene.store.RAMDirectory;
 
 /**
  * Tests changing the norms after changing the simularity
@@ -52,13 +57,12 @@
     /** inverts the normal notion of lengthNorm */
     public static Similarity s = new DefaultSimilarity() {
 	    public float lengthNorm(String fieldName, int numTokens) {
-		return (float)numTokens;
+		return numTokens;
 	    }
 	};
     
     public void setUp() throws Exception {
-	IndexWriter writer = new
-	    IndexWriter(store, new SimpleAnalyzer(), true);
+	IndexWriter writer = new IndexWriter(store, new SimpleAnalyzer(), true, MaxFieldLength.UNLIMITED);
 	
 	for (int i = 0; i < NUM_DOCS; i++) {
 	    Document d = new Document();
@@ -79,9 +83,9 @@
     }
     
     public void testMissingField() {
-	LengthNormModifier lnm = new LengthNormModifier(store, s);
+	FieldNormModifier fnm = new FieldNormModifier(store, s);
 	try {
-	    lnm.reSetNorms("nobodyherebutuschickens");
+	    fnm.reSetNorms("nobodyherebutuschickens");
 	} catch (Exception e) {
 	    assertNull("caught something", e);
 	}
@@ -100,9 +104,9 @@
 
 	r.close();
 	
-	LengthNormModifier lnm = new LengthNormModifier(store, s);
+	FieldNormModifier fnm = new FieldNormModifier(store, s);
 	try {
-	    lnm.reSetNorms("nonorm");
+	    fnm.reSetNorms("nonorm");
 	} catch (Exception e) {
 	    assertNull("caught something", e);
 	}
@@ -129,14 +133,18 @@
 	
 	// default similarity should put docs with shorter length first
   searcher = new IndexSearcher(store);
-  searcher.search(new TermQuery(new Term("field", "word")), new MultiReaderHitCollector() {
-    private int docBase = -1;
-    public final void collect(int doc, float score) {
-      scores[doc + docBase] = score;
+  searcher.search(new TermQuery(new Term("field", "word")), new Collector() {
+    private int docBase = 0;
+    private Scorer scorer;
+    public final void collect(int doc) throws IOException {
+      scores[doc + docBase] = scorer.score();
     }
     public void setNextReader(IndexReader reader, int docBase) {
       this.docBase = docBase;
     }
+    public void setScorer(Scorer scorer) throws IOException {
+      this.scorer = scorer;
+    }
   });
   searcher.close();
 	
@@ -151,22 +159,26 @@
 	// override the norms to be inverted
 	Similarity s = new DefaultSimilarity() {
 		public float lengthNorm(String fieldName, int numTokens) {
-		    return (float)numTokens;
+		    return numTokens;
 		}
 	    };
-	LengthNormModifier lnm = new LengthNormModifier(store, s);
-	lnm.reSetNorms("field");
+	FieldNormModifier fnm = new FieldNormModifier(store, s);
+	fnm.reSetNorms("field");
 
 	// new norm (with default similarity) should put longer docs first
 	searcher = new IndexSearcher(store);
-	searcher.search(new TermQuery(new Term("field", "word")), new MultiReaderHitCollector() {
-      private int docBase = -1;
-      public final void collect(int doc, float score) {
-        scores[doc + docBase] = score;
+	searcher.search(new TermQuery(new Term("field", "word")), new Collector() {
+      private int docBase = 0;
+      private Scorer scorer;
+      public final void collect(int doc) throws IOException {
+        scores[doc + docBase] = scorer.score();
       }
       public void setNextReader(IndexReader reader, int docBase) {
         this.docBase = docBase;
       }
+      public void setScorer(Scorer scorer) throws IOException {
+        this.scorer = scorer;
+      }
     });
     searcher.close();
 	

Modified: lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java (original)
+++ lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java Mon Apr 13 18:33:56 2009
@@ -21,7 +21,6 @@
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.FieldComparator;
 import org.apache.lucene.search.FieldComparatorSource;
 import org.apache.lucene.search.SortField;
@@ -49,8 +48,7 @@
 	}
 
 	@Override
-	public FieldComparator newComparator(String fieldname,
-			IndexReader[] subReaders, int numHits, int sortPos, boolean reversed)
+	public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed)
 			throws IOException {
 		dsdlc = new DistanceScoreDocLookupComparator(distanceFilter, numHits);
 		return dsdlc;
@@ -87,7 +85,7 @@
 		}
 
 		@Override
-		public int compareBottom(int doc, float score) {
+		public int compareBottom(int doc) {
 			final double v2 = distanceFilter.getDistance(doc);
       if (bottom > v2) {
         return 1;
@@ -98,7 +96,7 @@
 		}
 
 		@Override
-		public void copy(int slot, int doc, float score) {
+		public void copy(int slot, int doc) {
 			values[slot] = distanceFilter.getDistance(doc);
 		}
 

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer.java Mon Apr 13 18:33:56 2009
@@ -80,11 +80,11 @@
     public boolean done;
     public boolean required = false;
     public boolean prohibited = false;
-    public MultiReaderHitCollector collector;
+    public Collector collector;
     public SubScorer next;
 
     public SubScorer(Scorer scorer, boolean required, boolean prohibited,
-        MultiReaderHitCollector collector, SubScorer next)
+        Collector collector, SubScorer next)
       throws IOException {
       this.scorer = scorer;
       this.done = !scorer.next();
@@ -128,18 +128,32 @@
   private int end;
   private Bucket current;
 
+  /** @deprecated use {@link #score(Collector)} instead. */
   public void score(HitCollector hc) throws IOException {
     next();
     score(hc, Integer.MAX_VALUE);
   }
+  
+  public void score(Collector collector) throws IOException {
+    next();
+    score(collector, Integer.MAX_VALUE);
+  }
 
+  /** @deprecated use {@link #score(Collector, int)} instead. */
   protected boolean score(HitCollector hc, int max) throws IOException {
+    return score(new HitCollectorWrapper(hc), max);
+  }
+
+  protected boolean score(Collector collector, int max) throws IOException {
     if (coordFactors == null)
       computeCoordFactors();
 
     boolean more;
     Bucket tmp;
     
+    BucketScorer bs = new BucketScorer();
+    // The internal loop will set the score and doc before calling collect.
+    collector.setScorer(bs);
     do {
       bucketTable.first = null;
       
@@ -158,7 +172,9 @@
           }
           
           if (current.coord >= minNrShouldMatch) {
-            hc.collect(current.doc, current.score * coordFactors[current.coord]);
+            bs.score = current.score * coordFactors[current.coord];
+            bs.doc = current.doc;
+            collector.collect(current.doc);
           }
         }
         
@@ -210,8 +226,9 @@
       end += BucketTable.SIZE;
       for (SubScorer sub = scorers; sub != null; sub = sub.next) {
         Scorer scorer = sub.scorer;
+        sub.collector.setScorer(scorer);
         while (!sub.done && scorer.doc() < end) {
-          sub.collector.collect(scorer.doc(), scorer.score());
+          sub.collector.collect(scorer.doc());
           sub.done = !scorer.next();
         }
         if (!sub.done) {
@@ -237,6 +254,42 @@
     Bucket      next;                             // next valid bucket
   }
 
+  // An internal class which is used in score(Collector, int) for setting the
+  // current score. This is required since Collector exposes a setScorer method
+  // and implementations that need the score will call scorer.score().
+  // Therefore the only methods that are implemented are score() and doc().
+  private static final class BucketScorer extends Scorer {
+
+    float score;
+    int doc;
+    
+    public BucketScorer() {
+      super(null);
+    }
+    
+    
+    public Explanation explain(int doc) throws IOException {
+      return null;
+    }
+
+    public float score() throws IOException {
+      return score;
+    }
+
+    public int doc() {
+      return doc;
+    }
+
+    public boolean next() throws IOException {
+      return false;
+    }
+
+    public boolean skipTo(int target) throws IOException {
+      return false;
+    }
+    
+  }
+  
   /** A simple hash table of document scores within a range. */
   static final class BucketTable {
     public static final int SIZE = 1 << 11;
@@ -249,19 +302,25 @@
 
     public final int size() { return SIZE; }
 
-    public MultiReaderHitCollector newCollector(int mask) {
-      return new Collector(mask, this);
+    public Collector newCollector(int mask) {
+      return new BolleanScorerCollector(mask, this);
     }
   }
 
-  static final class Collector extends MultiReaderHitCollector {
+  private static final class BolleanScorerCollector extends Collector {
     private BucketTable bucketTable;
     private int mask;
-    public Collector(int mask, BucketTable bucketTable) {
+    private Scorer scorer;
+    
+    public BolleanScorerCollector(int mask, BucketTable bucketTable) {
       this.mask = mask;
       this.bucketTable = bucketTable;
     }
-    public final void collect(final int doc, final float score) {
+    public void setScorer(Scorer scorer) throws IOException {
+      this.scorer = scorer;
+    }
+    
+    public final void collect(final int doc) throws IOException {
       final BucketTable table = bucketTable;
       final int i = doc & BucketTable.MASK;
       Bucket bucket = table.buckets[i];
@@ -270,14 +329,14 @@
       
       if (bucket.doc != doc) {                    // invalid bucket
         bucket.doc = doc;                         // set doc
-        bucket.score = score;                     // initialize score
+        bucket.score = scorer.score();            // initialize score
         bucket.bits = mask;                       // initialize mask
         bucket.coord = 1;                         // initialize coord
 
         bucket.next = table.first;                // push onto valid list
         table.first = bucket;
       } else {                                    // valid bucket
-        bucket.score += score;                    // increment score
+        bucket.score += scorer.score();           // increment score
         bucket.bits |= mask;                      // add bits in mask
         bucket.coord++;                           // increment coord
       }

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer2.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer2.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer2.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/BooleanScorer2.java Mon Apr 13 18:33:56 2009
@@ -300,8 +300,17 @@
    * @param hc The collector to which all matching documents are passed through
    * {@link HitCollector#collect(int, float)}.
    * <br>When this method is used the {@link #explain(int)} method should not be used.
+   * @deprecated use {@link #score(Collector)} instead.
    */
   public void score(HitCollector hc) throws IOException {
+    score(new HitCollectorWrapper(hc));
+  }
+
+  /** Scores and collects all matching documents.
+   * @param collector The collector to which all matching documents are passed through.
+   * <br>When this method is used the {@link #explain(int)} method should not be used.
+   */
+  public void score(Collector collector) throws IOException {
     if (allowDocsOutOfOrder && requiredScorers.size() == 0
             && prohibitedScorers.size() < 32) {
       // fall back to BooleanScorer, scores documents somewhat out of order
@@ -314,13 +323,14 @@
       while (si.hasNext()) {
         bs.add((Scorer) si.next(), false /* required */, true /* prohibited */);
       }
-      bs.score(hc);
+      bs.score(collector);
     } else {
       if (countingSumScorer == null) {
         initCountingSumScorer();
       }
+      collector.setScorer(this);
       while (countingSumScorer.next()) {
-        hc.collect(countingSumScorer.doc(), score());
+        collector.collect(countingSumScorer.doc());
       }
     }
   }
@@ -332,12 +342,25 @@
    * {@link HitCollector#collect(int, float)}.
    * @param max Do not score documents past this.
    * @return true if more matching documents may remain.
+   * @deprecated use {@link #score(Collector, int)} instead.
    */
   protected boolean score(HitCollector hc, int max) throws IOException {
+    return score(new HitCollectorWrapper(hc), max);
+  }
+  
+  /** Expert: Collects matching documents in a range.
+   * <br>Note that {@link #next()} must be called once before this method is
+   * called for the first time.
+   * @param collector The collector to which all matching documents are passed through.
+   * @param max Do not score documents past this.
+   * @return true if more matching documents may remain.
+   */
+  protected boolean score(Collector collector, int max) throws IOException {
     // null pointer exception when next() was not called before:
     int docNr = countingSumScorer.doc();
+    collector.setScorer(this);
     while (docNr < max) {
-      hc.collect(docNr, score());
+      collector.collect(docNr);
       if (! countingSumScorer.next()) {
         return false;
       }

Added: lucene/java/trunk/src/java/org/apache/lucene/search/Collector.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/Collector.java?rev=764551&view=auto
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/Collector.java (added)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/Collector.java Mon Apr 13 18:33:56 2009
@@ -0,0 +1,160 @@
+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 org.apache.lucene.index.IndexReader;
+
+/**
+ * <p>Expert: Collectors are primarily meant to be used to
+ * gather raw results from a search, and implement sorting
+ * or custom result filtering, collation, etc. </p>
+ *
+ * <p>As of 2.9, this class replaces the deprecated
+ * HitCollector, and offers an API for efficient collection
+ * of hits across sequential {@link IndexReader}s. {@link
+ * IndexSearcher} advances the collector through each of the
+ * sub readers, in an arbitrary order. This results in a
+ * higher performance means of collection.</p>
+ *
+ * <p>Lucene's core collectors are derived from Collector.
+ * Likely your application can use one of these classes, or
+ * subclass {@link TopDocsCollector}, instead of
+ * implementing Collector directly:
+ *
+ * <ul>
+ *      
+ *   <li>{@link TopDocsCollector} is an abstract base class
+ *   that assumes you will retrieve the top N docs,
+ *   according to some criteria, after collection is
+ *   done.  </li>
+ *
+ *   <li>{@link TopScoreDocCollector} is a concrete subclass
+ *   {@link TopDocsCollector} and sorts according to score +
+ *   docID.  This is used internally by the {@link
+ *   IndexSearcher} search methods that do not take an
+ *   explicit {@link Sort}. It is likely the most frequently
+ *   used collector.</li>
+ *
+ *   <li>{@link TopFieldCollector} subclasses {@link
+ *   TopDocsCollector} and sorts according to a specified
+ *   {@link Sort} object (sort by field).  This is used
+ *   internally by the {@link IndexSearcher} search methods
+ *   that take an explicit {@link Sort}.
+ *
+ *   <li>{@link TimeLimitingCollector}, which wraps any other
+ *   Collector and aborts the search if it's taken too much
+ *   time, will subclass Collector in 3.0 (presently it
+ *   subclasses the deprecated HitCollector).</li>
+ *
+ *   <li>{@link PositiveScoresOnlyCollector} wraps any other
+ *   Collector and prevents collection of hits whose score
+ *   is &lt;= 0.0</li>
+ *
+ * </ul>
+ *
+ * <p>Collector decouples the score from the collected doc:
+ * the score computation is skipped entirely if it's not
+ * needed.  Collectors that do need the score should
+ * implement the {@link #setScorer} method, to hold onto the
+ * passed {@link Scorer} instance, and call {@link
+ * Scorer#score()} within the collect method to compute the
+ * current hit's score.  If your collector may request the
+ * score for a single hit multiple times, you should use
+ * {@link ScoreCachingWrappingScorer}. </p>
+ * 
+ * <p><b>NOTE:</b> The doc that is passed to the collect
+ * method is relative to the current reader. If your
+ * collector needs to resolve this to the docID space of the
+ * Multi*Reader, you must re-base it by recording the
+ * docBase from the most recent setNextReader call.  Here's
+ * a simple example showing how to collect docIDs into a
+ * BitSet:</p>
+ * 
+ * <pre>
+ * Searcher searcher = new IndexSearcher(indexReader);
+ * final BitSet bits = new BitSet(indexReader.maxDoc());
+ * searcher.search(query, new Collector() {
+ *   private int docBase;
+ * 
+ *   // ignore scorer
+ *   public void setScorer(Scorer scorer) {
+ *   }
+ * 
+ *   public void collect(int doc) {
+ *     bits.set(doc + docBase);
+ *   }
+ * 
+ *   public void setNextReader(IndexReader reader, int docBase) {
+ *     this.docBase = docBase;
+ *   }
+ * });
+ * </pre>
+ *
+ * <p>Not all collectors will need to rebase the docID.  For
+ * example, a collector that simply counts the total number
+ * of hits would skip it.</p>
+ * 
+ * <p><b>NOTE:</b> Prior to 2.9, Lucene silently filtered
+ * out hits with score <= 0.  As of 2.9, the core Collectors
+ * no longer do that.  It's very unusual to have such hits
+ * (a negative query boost, or function query returning
+ * negative custom scores, could cause it to happen).  If
+ * you need that behavior, use {@link
+ * PositiveScoresOnlyCollector}.</p>
+ *
+ * <p><b>NOTE:</b> This API is experimental and might change
+ * in incompatible ways in the next release.</p>
+ */
+public abstract class Collector {
+  
+  /**
+   * Called before successive calls to {@link #collect(int)}. Implementations
+   * that need the score of the current document (passed-in to
+   * {@link #collect(int)}), should save the passed-in Scorer and call
+   * scorer.score() when needed.
+   */
+  public abstract void setScorer(Scorer scorer) throws IOException;
+  
+  /**
+   * Called once for every document matching a query, with the unbased document
+   * number.
+   * 
+   * <p>
+   * Note: This is called in an inner search loop. For good search performance,
+   * implementations of this method should not call {@link Searcher#doc(int)} or
+   * {@link org.apache.lucene.index.IndexReader#document(int)} on every hit.
+   * Doing so can slow searches by an order of magnitude or more.
+   */
+  public abstract void collect(int doc) throws IOException;
+
+  /**
+   * Called before collecting from each IndexReader. All doc ids in
+   * {@link #collect(int)} will correspond to reader.
+   * 
+   * Add docBase to the current IndexReaders internal document id to re-base ids
+   * in {@link #collect(int)}.
+   * 
+   * @param reader
+   *          next IndexReader
+   * @param docBase
+   */
+  public abstract void setNextReader(IndexReader reader, int docBase) throws IOException;
+  
+}

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/DisjunctionSumScorer.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/DisjunctionSumScorer.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/DisjunctionSumScorer.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/DisjunctionSumScorer.java Mon Apr 13 18:33:56 2009
@@ -112,10 +112,20 @@
    * @param hc The collector to which all matching documents are passed through
    * {@link HitCollector#collect(int, float)}.
    * <br>When this method is used the {@link #explain(int)} method should not be used.
+   * @deprecated use {@link #score(Collector)} instead.
    */
   public void score(HitCollector hc) throws IOException {
+    score(new HitCollectorWrapper(hc));
+  }
+  
+  /** Scores and collects all matching documents.
+   * @param collector The collector to which all matching documents are passed through.
+   * <br>When this method is used the {@link #explain(int)} method should not be used.
+   */
+  public void score(Collector collector) throws IOException {
+    collector.setScorer(this);
     while (next()) {
-      hc.collect(currentDoc, currentScore);
+      collector.collect(currentDoc);
     }
   }
 
@@ -126,10 +136,23 @@
    * {@link HitCollector#collect(int, float)}.
    * @param max Do not score documents past this.
    * @return true if more matching documents may remain.
+   * @deprecated use {@link #score(Collector, int)} instead.
    */
   protected boolean score(HitCollector hc, int max) throws IOException {
+    return score(new HitCollectorWrapper(hc), max);
+  }
+  
+  /** Expert: Collects matching documents in a range.  Hook for optimization.
+   * Note that {@link #next()} must be called once before this method is called
+   * for the first time.
+   * @param collector The collector to which all matching documents are passed through.
+   * @param max Do not score documents past this.
+   * @return true if more matching documents may remain.
+   */
+  protected boolean score(Collector collector, int max) throws IOException {
+    collector.setScorer(this);
     while (currentDoc < max) {
-      hc.collect(currentDoc, currentScore);
+      collector.collect(currentDoc);
       if (!next()) {
         return false;
       }

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparator.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparator.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparator.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparator.java Mon Apr 13 18:33:56 2009
@@ -62,11 +62,11 @@
       return values[slot1] - values[slot2];
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       return bottom - currentReaderValues[doc];
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -87,7 +87,7 @@
     public Comparable value(int slot) {
       return new Byte(values[slot]);
     }
-  };
+  }
 
   /** Sorts by ascending docID */
   public static final class DocComparator extends FieldComparator {
@@ -104,12 +104,12 @@
       return docIDs[slot1] - docIDs[slot2];
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       // No overflow risk because docIDs are non-negative
       return bottom - (docBase + doc);
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       docIDs[slot] = docBase + doc;
     }
 
@@ -131,7 +131,7 @@
     public Comparable value(int slot) {
       return new Integer(docIDs[slot]);
     }
-  };
+  }
 
   /** Parses field's values as double (using {@link
    *  ExtendedFieldCache#getDoubles} and sorts by ascending value */
@@ -160,7 +160,7 @@
       }
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       final double v2 = currentReaderValues[doc];
       if (bottom > v2) {
         return 1;
@@ -171,7 +171,7 @@
       }
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -192,7 +192,7 @@
     public Comparable value(int slot) {
       return new Double(values[slot]);
     }
-  };
+  }
 
   /** Parses field's values as float (using {@link
    *  FieldCache#getFloats} and sorts by ascending value */
@@ -223,7 +223,7 @@
       }
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       // TODO: are there sneaky non-branch ways to compute
       // sign of float?
       final float v2 = currentReaderValues[doc];
@@ -236,7 +236,7 @@
       }
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -256,7 +256,7 @@
     public Comparable value(int slot) {
       return new Float(values[slot]);
     }
-  };
+  }
 
   /** Parses field's values as int (using {@link
    *  FieldCache#getInts} and sorts by ascending value */
@@ -289,7 +289,7 @@
       }
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       // TODO: there are sneaky non-branch ways to compute
       // -1/+1/0 sign
       // Cannot return bottom - values[slot2] because that
@@ -304,7 +304,7 @@
       }
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -324,7 +324,7 @@
     public Comparable value(int slot) {
       return new Integer(values[slot]);
     }
-  };
+  }
 
   /** Parses field's values as long (using {@link
    *  ExtendedFieldCache#getLongs} and sorts by ascending value */
@@ -355,7 +355,7 @@
       }
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       // TODO: there are sneaky non-branch ways to compute
       // -1/+1/0 sign
       final long v2 = currentReaderValues[doc];
@@ -368,7 +368,7 @@
       }
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -389,7 +389,7 @@
     public Comparable value(int slot) {
       return new Long(values[slot]);
     }
-  };
+  }
 
   /** Sorts by descending relevance.  NOTE: if you are
    *  sorting only by descending relevance and then
@@ -400,7 +400,8 @@
   public static final class RelevanceComparator extends FieldComparator {
     private final float[] scores;
     private float bottom;
-
+    private Scorer scorer;
+    
     RelevanceComparator(int numHits) {
       scores = new float[numHits];
     }
@@ -408,27 +409,16 @@
     public int compare(int slot1, int slot2) {
       final float score1 = scores[slot1];
       final float score2 = scores[slot2];
-      if (score1 > score2) {
-        return -1;
-      } else if (score1 < score2) {
-        return 1;
-      } else {
-        return 0;
-      }
+      return score1 > score2 ? -1 : (score1 < score2 ? 1 : 0);
     }
 
-    public int compareBottom(int doc, float score) {
-      if (bottom > score) {
-        return -1;
-      } else if (bottom < score) {
-        return 1;
-      } else {
-        return 0;
-      }
+    public int compareBottom(int doc) throws IOException {
+      float score = scorer.score();
+      return bottom > score ? -1 : (bottom < score ? 1 : 0);
     }
 
-    public void copy(int slot, int doc, float score) {
-      scores[slot] = score;
+    public void copy(int slot, int doc) throws IOException {
+      scores[slot] = scorer.score();
     }
 
     public void setNextReader(IndexReader reader, int docBase,  int numSlotsFull) {
@@ -438,6 +428,12 @@
       this.bottom = scores[bottom];
     }
 
+    public void setScorer(Scorer scorer) {
+      // wrap with a ScoreCachingWrappingScorer so that successive calls to
+      // score() will not incur score computation over and over again.
+      this.scorer = new ScoreCachingWrappingScorer(scorer);
+    }
+    
     public int sortType() {
       return SortField.SCORE;
     }
@@ -445,7 +441,7 @@
     public Comparable value(int slot) {
       return new Float(scores[slot]);
     }
-  };
+  }
 
   /** Parses field's values as short (using {@link
    *  FieldCache#getShorts} and sorts by ascending value */
@@ -466,11 +462,11 @@
       return values[slot1] - values[slot2];
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       return bottom - currentReaderValues[doc];
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -491,7 +487,7 @@
     public Comparable value(int slot) {
       return new Short(values[slot]);
     }
-  };
+  }
 
   /** Sorts by a field's value using the Collator for a
    *  given Locale.*/
@@ -523,7 +519,7 @@
       return collator.compare(val1, val2);
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       final String val2 = currentReaderValues[doc];
       if (bottom == null) {
         if (val2 == null) {
@@ -536,7 +532,7 @@
       return collator.compare(bottom, val2);
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -556,7 +552,7 @@
     public Comparable value(int slot) {
       return values[slot];
     }
-  };
+  }
 
   // NOTE: there were a number of other interesting String
   // comparators explored, but this one seemed to perform
@@ -608,7 +604,7 @@
       return val1.compareTo(val2);
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       assert bottomSlot != -1;
       int order = this.order[doc];
       final int cmp = bottomOrd - order;
@@ -659,7 +655,7 @@
       ords[slot] = index;
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       final int ord = order[doc];
       ords[slot] = ord;
       assert ord >= 0;
@@ -709,7 +705,7 @@
     public String getField() {
       return field;
     }
-  };
+  }
 
   /** Sorts by field's natural String sort order.  All
    *  comparisons are done using String.compareTo, which is
@@ -742,7 +738,7 @@
       return val1.compareTo(val2);
     }
 
-    public int compareBottom(int doc, float score) {
+    public int compareBottom(int doc) {
       final String val2 = currentReaderValues[doc];
       if (bottom == null) {
         if (val2 == null) {
@@ -755,7 +751,7 @@
       return bottom.compareTo(val2);
     }
 
-    public void copy(int slot, int doc, float score) {
+    public void copy(int slot, int doc) {
       values[slot] = currentReaderValues[doc];
     }
 
@@ -775,11 +771,11 @@
     public Comparable value(int slot) {
       return values[slot];
     }
-  };
+  }
 
   final protected static int binarySearch(String[] a, String key) {
     return binarySearch(a, key, 0, a.length-1);
-  };
+  }
 
   final protected static int binarySearch(String[] a, String key, int low, int high) {
 
@@ -801,7 +797,7 @@
         return mid;
     }
     return -(low + 1);
-  };
+  }
 
   /**
    * Compare hit at slot1 with hit at slot2.  Return 
@@ -827,22 +823,20 @@
    * only invoked after setBottom has been called.  
    * 
    * @param doc that was hit
-   * @param score of the hit
    * @return any N < 0 if the doc's value is sorted after
    * the bottom entry (not competitive), any N > 0 if the
    * doc's value is sorted before the bottom entry and 0 if
    * they are equal.
    */
-  public abstract int compareBottom(int doc, float score);
+  public abstract int compareBottom(int doc) throws IOException;
 
   /**
    * Copy hit (doc,score) to hit slot.
    * 
    * @param slot which slot to copy the hit to
    * @param doc docID relative to current reader
-   * @param score hit score
    */
-  public abstract void copy(int slot, int doc, float score);
+  public abstract void copy(int slot, int doc) throws IOException;
 
   /**
    * Set a new Reader. All doc correspond to the current Reader.
@@ -854,6 +848,12 @@
    */
   public abstract void setNextReader(IndexReader reader, int docBase, int numSlotsFull) throws IOException;
 
+  /** Sets the Scorer to use in case a document's score is needed. */
+  public void setScorer(Scorer scorer) {
+    // Empty implementation since most comparators don't need the score. This
+    // can be overridden by those that need it.
+  }
+  
   /**
    * @return SortField.TYPE
    */

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparatorSource.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparatorSource.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparatorSource.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/FieldComparatorSource.java Mon Apr 13 18:33:56 2009
@@ -18,7 +18,6 @@
  */
 
 import java.io.IOException;
-import org.apache.lucene.index.IndexReader;
 
 /**
  * Provides a {@link FieldComparator} for custom field sorting.
@@ -38,6 +37,6 @@
    * @throws IOException
    *           If an error occurs reading the index.
    */
-  public abstract FieldComparator newComparator(String fieldname, IndexReader[] subReaders, int numHits, int sortPos, boolean reversed)
+  public abstract FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed)
       throws IOException;
 }

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/FieldValueHitQueue.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/FieldValueHitQueue.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/FieldValueHitQueue.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/FieldValueHitQueue.java Mon Apr 13 18:33:56 2009
@@ -17,13 +17,13 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermEnum;
 import org.apache.lucene.util.PriorityQueue;
 
-import java.io.IOException;;
-
 /**
  * Expert: A hit queue for sorting by hits by terms in more than one field.
  * Uses <code>FieldCache.DEFAULT</code> for maintaining
@@ -32,12 +32,12 @@
  * <b>NOTE:</b> This API is experimental and might change in
  * incompatible ways in the next release.
  *
- * @since   lucene 2.9
+ * @since 2.9
  * @version $Id:
  * @see Searcher#search(Query,Filter,int,Sort)
  * @see FieldCache
  */
-public class FieldValueHitQueue extends PriorityQueue {
+public abstract class FieldValueHitQueue extends PriorityQueue {
 
   final static class Entry {
     int slot;
@@ -56,136 +56,185 @@
   }
 
   /**
-   * Creates a hit queue sorted by the given list of fields.
-   * @param fields SortField array we are sorting by in
-   *   priority order (highest priority first); cannot be <code>null</code> or empty
-   * @param size  The number of hits to retain.  Must be
-   *   greater than zero.
-   * @param subReaders Array of IndexReaders we will search,
-   *   in order that they will be searched
-   * @throws IOException
+   * An implementation of {@link FieldValueHitQueue} which is optimized in case
+   * there is just one comparator.
    */
-  public FieldValueHitQueue(SortField[] fields, int size, IndexReader[] subReaders) throws IOException {
-    numComparators = fields.length;
-    comparators = new FieldComparator[numComparators];
-    reverseMul = new int[numComparators];
-
-    if (fields.length == 0) {
-      throw new IllegalArgumentException("Sort must contain at least one field");
-    }
+  private static final class OneComparatorFieldValueHitQueue extends FieldValueHitQueue {
 
-    this.fields = fields;
-    for (int i=0; i<numComparators; ++i) {
-      SortField field = fields[i];
+    private final FieldComparator comparator;
+    private final int oneReverseMul;
+    
+    public OneComparatorFieldValueHitQueue(SortField[] fields, int size)
+        throws IOException {
+      super(fields);
+      if (fields.length == 0) {
+        throw new IllegalArgumentException("Sort must contain at least one field");
+      }
 
+      SortField field = fields[0];
       // AUTO is resolved before we are called
       assert field.getType() != SortField.AUTO;
+      comparator = field.getComparator(size, 0, field.reverse);
+      oneReverseMul = field.reverse ? -1 : 1;
 
-      reverseMul[i] = field.reverse ? -1 : 1;
-      comparators[i] = field.getComparator(subReaders, size, i, field.reverse);
+      comparators[0] = comparator;
+      reverseMul[0] = oneReverseMul;
+      
+      initialize(size);
     }
 
-    if (numComparators == 1) {
-      comparator1 = comparators[0];
-      reverseMul1 = reverseMul[0];
-    } else {
-      comparator1 = null;
-      reverseMul1 = 0;
+    /**
+     * Returns whether <code>a</code> is less relevant than <code>b</code>.
+     * @param a ScoreDoc
+     * @param b ScoreDoc
+     * @return <code>true</code> if document <code>a</code> should be sorted after document <code>b</code>.
+     */
+    protected boolean lessThan(final Object a, final Object b) {
+      final Entry hitA = (Entry) a;
+      final Entry hitB = (Entry) b;
+
+      assert hitA != hitB;
+      assert hitA.slot != hitB.slot;
+
+      final int c = oneReverseMul * comparator.compare(hitA.slot, hitB.slot);
+      if (c != 0) {
+        return c > 0;
+      }
+
+      // avoid random sort order that could lead to duplicates (bug #31241):
+      return hitA.docID > hitB.docID;
     }
 
-    initialize(size);
   }
   
-  /** Stores a comparator corresponding to each field being sorted by */
-  private final FieldComparator[] comparators;
-  private final FieldComparator comparator1;
-  private final int numComparators;
-  private final int[] reverseMul;
-  private final int reverseMul1;
+  /**
+   * An implementation of {@link FieldValueHitQueue} which is optimized in case
+   * there is more than one comparator.
+   */
+  private static final class MultiComparatorsFieldValueHitQueue extends FieldValueHitQueue {
 
-  FieldComparator[] getComparators() {
-    return comparators;
-  }
+    public MultiComparatorsFieldValueHitQueue(SortField[] fields, int size)
+        throws IOException {
+      super(fields);
 
-  int[] getReverseMul() {
-    return reverseMul;
-  }
+      int numComparators = comparators.length;
+      for (int i = 0; i < numComparators; ++i) {
+        SortField field = fields[i];
 
-  /** Stores the sort criteria being used. */
-  private final SortField[] fields;
+        // AUTO is resolved before we are called
+        assert field.getType() != SortField.AUTO;
 
-  /**
-   * Returns whether <code>a</code> is less relevant than <code>b</code>.
-   * @param a ScoreDoc
-   * @param b ScoreDoc
-   * @return <code>true</code> if document <code>a</code> should be sorted after document <code>b</code>.
-   */
-  protected boolean lessThan (final Object a, final Object b) {
-    final Entry hitA = (Entry) a;
-    final Entry hitB = (Entry) b;
-
-    assert hitA != hitB;
-    assert hitA.slot != hitB.slot;
-
-    if (numComparators == 1) {
-      // Common case
-      final int c = reverseMul1 * comparator1.compare(hitA.slot, hitB.slot);
-      if (c != 0) {
-        return c > 0;
+        reverseMul[i] = field.reverse ? -1 : 1;
+        comparators[i] = field.getComparator(size, i, field.reverse);
       }
-    } else {
-      // run comparators
-      for (int i=0; i<numComparators; ++i) {
+
+      initialize(size);
+    }
+  
+    protected boolean lessThan(Object a, Object b) {
+      final Entry hitA = (Entry) a;
+      final Entry hitB = (Entry) b;
+
+      assert hitA != hitB;
+      assert hitA.slot != hitB.slot;
+
+      int numComparators = comparators.length;
+      for (int i = 0; i < numComparators; ++i) {
         final int c = reverseMul[i] * comparators[i].compare(hitA.slot, hitB.slot);
         if (c != 0) {
           // Short circuit
           return c > 0;
         }
       }
+
+      // avoid random sort order that could lead to duplicates (bug #31241):
+      return hitA.docID > hitB.docID;
     }
+    
+  }
+  
+  // prevent instantiation and extension.
+  private FieldValueHitQueue(SortField[] fields) {
+    // When we get here, fields.length is guaranteed to be > 0, therefore no
+    // need to check it again.
+    
+    // All these are required by this class's API - need to return arrays.
+    // Therefore even in the case of a single comparator, create an array
+    // anyway.
+    this.fields = fields;
+    int numComparators = fields.length;
+    comparators = new FieldComparator[numComparators];
+    reverseMul = new int[numComparators];
+  }
 
-    // avoid random sort order that could lead to duplicates (bug #31241):
-    return hitA.docID > hitB.docID;
+  /**
+   * Creates a hit queue sorted by the given list of fields.
+   * 
+   * @param fields
+   *          SortField array we are sorting by in priority order (highest
+   *          priority first); cannot be <code>null</code> or empty
+   * @param size
+   *          The number of hits to retain. Must be greater than zero.
+   * @throws IOException
+   */
+  public static FieldValueHitQueue create(SortField[] fields, int size) throws IOException {
+
+    if (fields.length == 0) {
+      throw new IllegalArgumentException("Sort must contain at least one field");
+    }
+
+    if (fields.length == 1) {
+      return new OneComparatorFieldValueHitQueue(fields, size);
+    } else {
+      return new MultiComparatorsFieldValueHitQueue(fields, size);
+    }
   }
+  
+  FieldComparator[] getComparators() { return comparators; }
 
+  int[] getReverseMul() { return reverseMul; }
+
+  /** Stores the sort criteria being used. */
+  protected final SortField[] fields;
+  protected final FieldComparator[] comparators;
+  protected final int[] reverseMul;
+
+  protected abstract boolean lessThan (final Object a, final Object b);
 
   /**
-   * Given a FieldDoc object, stores the values used
-   * to sort the given document.  These values are not the raw
-   * values out of the index, but the internal representation
-   * of them.  This is so the given search hit can be collated
-   * by a MultiSearcher with other search hits.
-   * @param  doc  The FieldDoc to store sort values into.
-   * @return  The same FieldDoc passed in.
+   * Given a FieldDoc object, stores the values used to sort the given document.
+   * These values are not the raw values out of the index, but the internal
+   * representation of them. This is so the given search hit can be collated by
+   * a MultiSearcher with other search hits.
+   * 
+   * @param doc
+   *          The FieldDoc to store sort values into.
+   * @return The same FieldDoc passed in.
    * @see Searchable#search(Weight,Filter,int,Sort)
    */
-  FieldDoc fillFields (final Entry entry) {
+  FieldDoc fillFields(final Entry entry) {
     final int n = comparators.length;
     final Comparable[] fields = new Comparable[n];
-    for (int i=0; i<n; ++i)
+    for (int i = 0; i < n; ++i) {
       fields[i] = comparators[i].value(entry.slot);
+    }
     //if (maxscore > 1.0f) doc.score /= maxscore;   // normalize scores
-    return new FieldDoc(entry.docID,
-                        entry.score,
-                        fields);
+    return new FieldDoc(entry.docID, entry.score, fields);
   }
 
-
   /** Returns the SortFields being used by this hit queue. */
   SortField[] getFields() {
     return fields;
   }
   
-  /**
-   * Attempts to detect the given field type for an IndexReader.
-   */
+  /** Attempts to detect the given field type for an IndexReader. */
   static int detectFieldType(IndexReader reader, String fieldKey) throws IOException {
-    String field = ((String)fieldKey).intern();
-    TermEnum enumerator = reader.terms (new Term (field));
+    String field = fieldKey.intern();
+    TermEnum enumerator = reader.terms(new Term(field));
     try {
       Term term = enumerator.term();
       if (term == null) {
-        throw new RuntimeException ("no terms in field " + field + " - cannot determine sort type");
+        throw new RuntimeException("no terms in field " + field + " - cannot determine sort type");
       }
       int ret = 0;
       if (term.field() == field) {
@@ -219,7 +268,7 @@
           }
         }         
       } else {
-        throw new RuntimeException ("field \"" + field + "\" does not appear to be indexed");
+        throw new RuntimeException("field \"" + field + "\" does not appear to be indexed");
       }
       return ret;
     } finally {

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/HitCollector.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/HitCollector.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/HitCollector.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/HitCollector.java Mon Apr 13 18:33:56 2009
@@ -17,13 +17,15 @@
  * limitations under the License.
  */
 
-/** Lower-level search API.
- * <br>HitCollectors are primarily meant to be used to implement queries,
- * sorting and filtering.  See {@link
- * MultiReaderHitCollector} for a lower level and
- * higher performance (on a multi-segment index) API.
+/**
+ * Lower-level search API. <br>
+ * HitCollectors are primarily meant to be used to implement queries, sorting
+ * and filtering. See {@link Collector} for a lower level and higher performance
+ * (on a multi-segment index) API.
+ * 
  * @see Searcher#search(Query,HitCollector)
  * @version $Id$
+ * @deprecated Please use {@link Collector} instead.
  */
 public abstract class HitCollector {
   /** Called once for every document matching a query, with the document

Added: lucene/java/trunk/src/java/org/apache/lucene/search/HitCollectorWrapper.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/HitCollectorWrapper.java?rev=764551&view=auto
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/HitCollectorWrapper.java (added)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/HitCollectorWrapper.java Mon Apr 13 18:33:56 2009
@@ -0,0 +1,50 @@
+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 org.apache.lucene.index.IndexReader;
+
+/**
+ * Wrapper for ({@link HitCollector}) implementations, which
+ * simply re-bases the incoming docID before calling {@link
+ * HitCollector#collect}.
+ * @deprecated this class will be removed when {@link HitCollector} is removed.
+ */
+class HitCollectorWrapper extends Collector {
+  private HitCollector collector;
+  private int base = 0;
+  private Scorer scorer = null;
+  
+  public HitCollectorWrapper(HitCollector collector) {
+    this.collector = collector;
+  }
+  
+  public void setNextReader(IndexReader reader, int docBase) {
+    base = docBase;
+  }
+
+  public void collect(int doc) throws IOException {
+    collector.collect(doc + base, scorer.score());
+  }
+
+  public void setScorer(Scorer scorer) throws IOException {
+    this.scorer = scorer;      
+  }
+}

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/IndexSearcher.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/IndexSearcher.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/IndexSearcher.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/IndexSearcher.java Mon Apr 13 18:33:56 2009
@@ -188,12 +188,16 @@
       throws IOException {
     return search(weight, filter, nDocs, sort, true);
   }
-  
-  /** 
-   * Just like {@link #search(Weight, Filter, int, Sort)},
-   * but you choose whether or not the fields in the
-   * returned {@link FieldDoc} instances should be set by
-   * specifying fillFields.
+
+  /**
+   * Just like {@link #search(Weight, Filter, int, Sort)}, but you choose
+   * whether or not the fields in the returned {@link FieldDoc} instances should
+   * be set by specifying fillFields.<br>
+   * <b>NOTE:</b> currently, this method tracks document scores and sets them in
+   * the returned {@link FieldDoc}, however in 3.0 it will move to not track
+   * document scores. If document scores tracking is still needed, you can use
+   * {@link #search(Weight, Filter, Collector)} and pass in a
+   * {@link TopFieldCollector} instance.
    */
   public TopFieldDocs search(Weight weight, Filter filter, final int nDocs,
                              Sort sort, boolean fillFields)
@@ -222,29 +226,32 @@
     
     if (legacy) {
       // Search the single top-level reader
-      TopScoreDocCollector collector = new TopFieldDocCollector(reader, sort, nDocs);
-      collector.setNextReader(reader, 0);
-      doSearch(reader, weight, filter, collector);
-      return (TopFieldDocs) collector.topDocs();
-    } else {
-      // Search each sub-reader
-      TopFieldCollector collector = new TopFieldCollector(sort, nDocs, sortedSubReaders, fillFields);
-      search(weight, filter, collector);
+      TopDocCollector collector = new TopFieldDocCollector(reader, sort, nDocs);
+      HitCollectorWrapper hcw = new HitCollectorWrapper(collector);
+      hcw.setNextReader(reader, 0);
+      doSearch(reader, weight, filter, hcw);
       return (TopFieldDocs) collector.topDocs();
     }
+    // Search each sub-reader
+    // TODO: by default we should create a TopFieldCollector which does not
+    // track document scores and maxScore. Currently the default is set to true,
+    // however it will change in 3.0.
+    TopFieldCollector collector = TopFieldCollector.create(sort, nDocs, fillFields, true, true);
+    search(weight, filter, collector);
+    return (TopFieldDocs) collector.topDocs();
   }
 
   // inherit javadoc
+  /** @deprecated use {@link #search(Weight, Filter, Collector)} instead. */
   public void search(Weight weight, Filter filter, HitCollector results)
       throws IOException {
-
-    final MultiReaderHitCollector collector;
-    if (results instanceof MultiReaderHitCollector) {
-      collector = (MultiReaderHitCollector) results;
-    } else {
-      collector = new MultiReaderCollectorWrapper(results);
-    }
-
+    search(weight, filter, new HitCollectorWrapper(results));
+  }
+  
+  // inherit javadoc
+  public void search(Weight weight, Filter filter, Collector collector)
+      throws IOException {
+    
     for (int i = 0; i < sortedSubReaders.length; i++) { // search each subreader
       collector.setNextReader(sortedSubReaders[i], sortedStarts[i]);
       doSearch(sortedSubReaders[i], weight, filter, collector);
@@ -252,14 +259,14 @@
   }
   
   private void doSearch(IndexReader reader, Weight weight, Filter filter,
-      final HitCollector results) throws IOException {
+      final Collector collector) throws IOException {
 
     Scorer scorer = weight.scorer(reader);
     if (scorer == null)
       return;
 
     if (filter == null) {
-      scorer.score(results);
+      scorer.score(collector);
       return;
     }
 
@@ -267,6 +274,7 @@
     
     boolean more = filterDocIdIterator.next() && scorer.skipTo(filterDocIdIterator.doc());
 
+    collector.setScorer(scorer);
     while (more) {
       int filterDocId = filterDocIdIterator.doc();
       if (filterDocId > scorer.doc() && !scorer.skipTo(filterDocId)) {
@@ -274,7 +282,7 @@
       } else {
         int scorerDocId = scorer.doc();
         if (scorerDocId == filterDocId) { // permitted by filter
-          results.collect(scorerDocId, scorer.score());
+          collector.collect(scorerDocId);
           more = filterDocIdIterator.next();
         } else {
           more = filterDocIdIterator.skipTo(scorerDocId);
@@ -295,26 +303,4 @@
   public Explanation explain(Weight weight, int doc) throws IOException {
     return weight.explain(reader, doc);
   }
-  
-  /**
-   * Wrapper for non expert ({@link HitCollector})
-   * implementations, which simply re-bases the incoming
-   * docID before calling {@link HitCollector#collect}.
-   */
-  static class MultiReaderCollectorWrapper extends MultiReaderHitCollector {
-    private HitCollector collector;
-    private int base = -1;
-
-    public MultiReaderCollectorWrapper(HitCollector collector) {
-      this.collector = collector;
-    }
-    
-    public void collect(int doc, float score) {
-      collector.collect(doc + base, score);
-    }
-
-    public void setNextReader(IndexReader reader, int docBase) {
-      base = docBase;
-    }
-  }
 }

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/MultiSearcher.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/MultiSearcher.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/MultiSearcher.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/MultiSearcher.java Mon Apr 13 18:33:56 2009
@@ -97,9 +97,14 @@
       throw new UnsupportedOperationException();
     }
 
+    /** @deprecated use {@link #search(Weight, Filter, Collector)} instead. */
     public void search(Weight weight, Filter filter, HitCollector results) {
       throw new UnsupportedOperationException();
     }
+    
+    public void search(Weight weight, Filter filter, Collector collector) {
+      throw new UnsupportedOperationException();
+    }
 
     public TopDocs search(Weight weight,Filter filter,int n) {
       throw new UnsupportedOperationException();
@@ -251,40 +256,31 @@
     return new TopFieldDocs (totalHits, scoreDocs, hq.getFields(), maxScore);
   }
 
-
   // inherit javadoc
+  /** @deprecated use {@link #search(Weight, Filter, Collector)} instead. */
   public void search(Weight weight, Filter filter, final HitCollector results)
     throws IOException {
+    search(weight, filter, new HitCollectorWrapper(results));
+  }
+  
+  // inherit javadoc
+  public void search(Weight weight, Filter filter, final Collector collector)
+  throws IOException {
     for (int i = 0; i < searchables.length; i++) {
-
+      
       final int start = starts[i];
-
-      final MultiReaderHitCollector hc;
-      if (results instanceof MultiReaderHitCollector) {
-        // results can shift
-        final MultiReaderHitCollector resultsMulti = (MultiReaderHitCollector) results;
-        hc = new MultiReaderHitCollector() {
-            public void collect(int doc, float score) {
-              resultsMulti.collect(doc, score);
-            }
-
-            public void setNextReader(IndexReader reader, int docBase) throws IOException {
-              resultsMulti.setNextReader(reader, start+docBase);
-            }
-          };
-      } else {
-        // We must shift the docIDs
-        hc = new MultiReaderHitCollector() {
-            private int docBase;
-            public void collect(int doc, float score) {
-              results.collect(doc + docBase + start, score);
-            }
-
-            public void setNextReader(IndexReader reader, int docBase) {
-              this.docBase = docBase;
-            }
-          };
-      }
+      
+      final Collector hc = new Collector() {
+        public void setScorer(Scorer scorer) throws IOException {
+          collector.setScorer(scorer);
+        }
+        public void collect(int doc) throws IOException {
+          collector.collect(doc);
+        }
+        public void setNextReader(IndexReader reader, int docBase) throws IOException {
+          collector.setNextReader(reader, start + docBase);
+        }
+      };
       
       searchables[i].search(weight, filter, hc);
     }

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/ParallelMultiSearcher.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/ParallelMultiSearcher.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/ParallelMultiSearcher.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/ParallelMultiSearcher.java Mon Apr 13 18:33:56 2009
@@ -170,44 +170,51 @@
    * @param results to receive hits
    * 
    * @todo parallelize this one too
+   * @deprecated use {@link #search(Weight, Filter, Collector)} instead.
    */
   public void search(Weight weight, Filter filter, final HitCollector results)
     throws IOException {
-    for (int i = 0; i < searchables.length; i++) {
-
-      final int start = starts[i];
-
-      final MultiReaderHitCollector hc;
-      if (results instanceof MultiReaderHitCollector) {
-        // results can shift
-        final MultiReaderHitCollector resultsMulti = (MultiReaderHitCollector) results;
-        hc = new MultiReaderHitCollector() {
-            public void collect(int doc, float score) {
-              resultsMulti.collect(doc, score);
-            }
-
-            public void setNextReader(IndexReader reader, int docBase) throws IOException {
-              resultsMulti.setNextReader(reader, start+docBase);
-            }
-          };
-      } else {
-        // We must shift the docIDs
-        hc = new MultiReaderHitCollector() {
-            private int docBase;
-            public void collect(int doc, float score) {
-              results.collect(doc + docBase + start, score);
-            }
-
-            public void setNextReader(IndexReader reader, int docBase) {
-              this.docBase = docBase;
-            }
-          };
-      }
-      
-      searchables[i].search(weight, filter, hc);
-    }
+    search(weight, filter, new HitCollectorWrapper(results));
   }
 
+  /** Lower-level search API.
+  *
+  * <p>{@link Collector#collect(int)} is called for every matching document.
+  *
+  * <p>Applications should only use this if they need <i>all</i> of the
+  * matching documents.  The high-level search API ({@link
+  * Searcher#search(Query)}) is usually more efficient, as it skips
+  * non-high-scoring hits.
+  *
+  * @param weight to match documents
+  * @param filter if non-null, a bitset used to eliminate some documents
+  * @param collector to receive hits
+  * 
+  * @todo parallelize this one too
+  */
+  public void search(Weight weight, Filter filter, final Collector collector)
+   throws IOException {
+   for (int i = 0; i < searchables.length; i++) {
+
+     final int start = starts[i];
+
+     final Collector hc = new Collector() {
+       public void setScorer(Scorer scorer) throws IOException {
+         collector.setScorer(scorer);
+       }
+       public void collect(int doc) throws IOException {
+         collector.collect(doc);
+       }
+       
+       public void setNextReader(IndexReader reader, int docBase) throws IOException {
+         collector.setNextReader(reader, start + docBase);
+       }
+     };
+     
+     searchables[i].search(weight, filter, hc);
+   }
+ }
+
   /*
    * TODO: this one could be parallelized too
    * @see org.apache.lucene.search.Searchable#rewrite(org.apache.lucene.search.Query)

Added: lucene/java/trunk/src/java/org/apache/lucene/search/PositiveScoresOnlyCollector.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/PositiveScoresOnlyCollector.java?rev=764551&view=auto
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/PositiveScoresOnlyCollector.java (added)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/PositiveScoresOnlyCollector.java Mon Apr 13 18:33:56 2009
@@ -0,0 +1,56 @@
+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 org.apache.lucene.index.IndexReader;
+
+/**
+ * A {@link Collector} implementation which wraps another
+ * {@link Collector} and makes sure only documents with
+ * scores &gt; 0 are collected.
+ */
+
+public class PositiveScoresOnlyCollector extends Collector {
+
+  final private Collector c;
+  private Scorer scorer;
+  
+  public PositiveScoresOnlyCollector(Collector c) {
+    this.c = c;
+  }
+  
+  public void collect(int doc) throws IOException {
+    if (scorer.score() > 0) {
+      c.collect(doc);
+    }
+  }
+
+  public void setNextReader(IndexReader reader, int docBase) throws IOException {
+    c.setNextReader(reader, docBase);
+  }
+
+  public void setScorer(Scorer scorer) throws IOException {
+    // Set a ScoreCachingWrappingScorer in case the wrapped Collector will call
+    // score() also.
+    this.scorer = new ScoreCachingWrappingScorer(scorer);
+    c.setScorer(this.scorer);
+  }
+
+}

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/QueryWrapperFilter.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/QueryWrapperFilter.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/QueryWrapperFilter.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/QueryWrapperFilter.java Mon Apr 13 18:33:56 2009
@@ -50,9 +50,12 @@
   public BitSet bits(IndexReader reader) throws IOException {
     final BitSet bits = new BitSet(reader.maxDoc());
 
-    new IndexSearcher(reader).search(query, new MultiReaderHitCollector() {
-      private int base = -1;
-      public final void collect(int doc, float score) {
+    new IndexSearcher(reader).search(query, new Collector() {
+      private int base = 0;
+      public void setScorer(Scorer scorer) throws IOException {
+        // score is not needed by this collector 
+      }
+      public final void collect(int doc) {
         bits.set(doc + base);  // set bit for hit
       }
       public void setNextReader(IndexReader reader, int docBase) {

Modified: lucene/java/trunk/src/java/org/apache/lucene/search/RemoteSearchable.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/RemoteSearchable.java?rev=764551&r1=764550&r2=764551&view=diff
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/RemoteSearchable.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/RemoteSearchable.java Mon Apr 13 18:33:56 2009
@@ -45,12 +45,17 @@
     this.local = local;
   }
 
-
+  /** @deprecated use {@link #search(Weight, Filter, Collector)} instead. */
   public void search(Weight weight, Filter filter, HitCollector results)
     throws IOException {
     local.search(weight, filter, results);
   }
 
+  public void search(Weight weight, Filter filter, Collector results)
+      throws IOException {
+    local.search(weight, filter, results);
+  }
+
   public void close() throws IOException {
     local.close();
   }

Added: lucene/java/trunk/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java?rev=764551&view=auto
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java (added)
+++ lucene/java/trunk/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java Mon Apr 13 18:33:56 2009
@@ -0,0 +1,83 @@
+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;
+
+/**
+ * A {@link Scorer} which wraps another scorer and caches the score of the
+ * current document. Successive calls to {@link #score()} will return the same
+ * result and will not invoke the wrapped Scorer's score() method, unless the
+ * current document has changed.<br>
+ * This class might be useful due to the changes done to the {@link Collector}
+ * interface, in which the score is not computed for a document by default, only
+ * if the collector requests it. Some collectors may need to use the score in
+ * several places, however all they have in hand is a {@link Scorer} object, and
+ * might end up computing the score of a document more than once.
+ */
+public class ScoreCachingWrappingScorer extends Scorer {
+
+  private Scorer scorer;
+  private int curDoc = -1;
+  private float curScore;
+  
+  /** Creates a new instance by wrapping the given scorer. */
+  public ScoreCachingWrappingScorer(Scorer scorer) {
+    super(scorer.getSimilarity());
+    this.scorer = scorer;
+  }
+
+  protected boolean score(Collector collector, int max) throws IOException {
+    return scorer.score(collector, max);
+  }
+
+  public Similarity getSimilarity() {
+    return scorer.getSimilarity();
+  }
+  
+  public Explanation explain(int doc) throws IOException {
+    return scorer.explain(doc);
+  }
+
+  public float score() throws IOException {
+    int doc = scorer.doc();
+    if (doc != curDoc) {
+      curScore = scorer.score();
+      curDoc = doc;
+    }
+    
+    return curScore;
+  }
+
+  public int doc() {
+    return scorer.doc();
+  }
+
+  public boolean next() throws IOException {
+    return scorer.next();
+  }
+
+  public void score(Collector collector) throws IOException {
+    scorer.score(collector);
+  }
+  
+  public boolean skipTo(int target) throws IOException {
+    return scorer.skipTo(target);
+  }
+
+}