LUCENE-1575: switch to new Collector API

git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@764551 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael McCandless 2009-04-13 18:33:56 +00:00
parent 413cabef1f
commit ad79d914e4
54 changed files with 3854 additions and 800 deletions

View File

@ -3,6 +3,32 @@ $Id$
======================= 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
@ -11,6 +37,19 @@ Changes in runtime behavior
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
1. LUCENE-1419: Add expert API to set custom indexing chain. This API is
@ -70,6 +109,14 @@ API Changes
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
1. LUCENE-1415: MultiPhraseQuery has incorrect hashCode() and equals()
@ -259,6 +306,12 @@ Optimizations
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
Build

View File

@ -32,6 +32,7 @@ import org.apache.lucene.document.Document;
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 abstract class ReadTask extends PerfTask {
public ReadTask(PerfRunData runData) {
super(runData);
}
public int doLogic() throws Exception {
int res = 0;
boolean closeReader = false;
@ -102,7 +102,10 @@ public abstract class ReadTask extends PerfTask {
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 class ReadTask extends PerfTask {
*/
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.

View File

@ -27,6 +27,8 @@ import org.apache.lucene.search.SortField;
*/
public class SearchWithSortTask extends ReadTask {
private boolean doScore = true;
private boolean doMaxScore = true;
private Sort sort;
public SearchWithSortTask(PerfRunData runData) {
@ -34,7 +36,12 @@ public class SearchWithSortTask extends ReadTask {
}
/**
* 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 @@ public class SearchWithSortTask extends ReadTask {
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 @@ public class SearchWithSortTask extends ReadTask {
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 @@ public class SearchWithSortTask extends ReadTask {
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");

View File

@ -22,16 +22,18 @@ import java.util.Arrays;
import junit.framework.TestCase;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiReaderHitCollector;
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.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.Scorer;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
/**
* Tests changing of field norms with a custom similarity and with fake norms.
@ -52,12 +54,12 @@ public class TestFieldNormModifier extends TestCase {
/** 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 @@ public class TestFieldNormModifier extends TestCase {
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 @@ public class TestFieldNormModifier extends TestCase {
// 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 @@ public class TestFieldNormModifier extends TestCase {
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();

View File

@ -17,21 +17,26 @@ package org.apache.lucene.misc;
* 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.index.IndexReader;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiReaderHitCollector;
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.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.Scorer;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
/**
* Tests changing the norms after changing the simularity
@ -52,13 +57,12 @@ public class TestLengthNormModifier extends TestCase {
/** 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 class TestLengthNormModifier extends TestCase {
}
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 @@ public class TestLengthNormModifier extends TestCase {
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 @@ public class TestLengthNormModifier extends TestCase {
// 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 @@ public class TestLengthNormModifier extends TestCase {
// 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();

View File

@ -21,7 +21,6 @@ import java.io.IOException;
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 @@ public class DistanceFieldComparatorSource extends FieldComparatorSource {
}
@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 @@ public class DistanceFieldComparatorSource extends FieldComparatorSource {
}
@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 @@ public class DistanceFieldComparatorSource extends FieldComparatorSource {
}
@Override
public void copy(int slot, int doc, float score) {
public void copy(int slot, int doc) {
values[slot] = distanceFilter.getDistance(doc);
}

View File

@ -80,11 +80,11 @@ final class BooleanScorer extends Scorer {
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 @@ final class BooleanScorer extends Scorer {
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 @@ final class BooleanScorer extends Scorer {
}
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 @@ final class BooleanScorer extends Scorer {
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 @@ final class BooleanScorer extends Scorer {
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 @@ final class BooleanScorer extends Scorer {
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 @@ final class BooleanScorer extends Scorer {
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
}

View File

@ -300,8 +300,17 @@ class BooleanScorer2 extends Scorer {
* @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 @@ class BooleanScorer2 extends Scorer {
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 @@ class BooleanScorer2 extends Scorer {
* {@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;
}

View File

@ -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;
}

View File

@ -112,10 +112,20 @@ class DisjunctionSumScorer extends Scorer {
* @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 @@ class DisjunctionSumScorer extends Scorer {
* {@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;
}

View File

@ -62,11 +62,11 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
}
}
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 abstract class FieldComparator {
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,6 +400,7 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
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 abstract class FieldComparator {
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 @@ public abstract class FieldComparator {
return mid;
}
return -(low + 1);
};
}
/**
* Compare hit at slot1 with hit at slot2. Return
@ -827,22 +823,20 @@ public abstract class FieldComparator {
* 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 class FieldComparator {
*/
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
*/

View File

@ -18,7 +18,6 @@ package org.apache.lucene.search;
*/
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
/**
* Provides a {@link FieldComparator} for custom field sorting.
@ -38,6 +37,6 @@ public abstract class FieldComparatorSource {
* @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;
}

View File

@ -17,13 +17,13 @@ package org.apache.lucene.search;
* 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 @@ import java.io.IOException;;
* <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 @@ public class FieldValueHitQueue extends PriorityQueue {
}
/**
* 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];
private static final class OneComparatorFieldValueHitQueue extends FieldValueHitQueue {
if (fields.length == 0) {
throw new IllegalArgumentException("Sort must contain at least one field");
}
private final FieldComparator comparator;
private final int oneReverseMul;
this.fields = fields;
for (int i=0; i<numComparators; ++i) {
SortField field = fields[i];
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;
initialize(size);
}
assert hitA != hitB;
assert hitA.slot != hitB.slot;
/** 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;
FieldComparator[] getComparators() {
return comparators;
}
int[] getReverseMul() {
return reverseMul;
}
/** Stores the sort criteria being used. */
private final SortField[] fields;
/**
* 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);
final int c = oneReverseMul * comparator.compare(hitA.slot, hitB.slot);
if (c != 0) {
return c > 0;
}
} else {
// run comparators
for (int i=0; i<numComparators; ++i) {
// avoid random sort order that could lead to duplicates (bug #31241):
return hitA.docID > hitB.docID;
}
}
/**
* An implementation of {@link FieldValueHitQueue} which is optimized in case
* there is more than one comparator.
*/
private static final class MultiComparatorsFieldValueHitQueue extends FieldValueHitQueue {
public MultiComparatorsFieldValueHitQueue(SortField[] fields, int size)
throws IOException {
super(fields);
int numComparators = comparators.length;
for (int i = 0; i < numComparators; ++i) {
SortField field = fields[i];
// AUTO is resolved before we are called
assert field.getType() != SortField.AUTO;
reverseMul[i] = field.reverse ? -1 : 1;
comparators[i] = field.getComparator(size, i, field.reverse);
}
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;
}
// 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];
}
/**
* 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)
* 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
*/
FieldDoc fillFields (final Entry entry) {
final int n = comparators.length;
final Comparable[] fields = new Comparable[n];
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);
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.
* @see Searchable#search(Weight,Filter,int,Sort)
*/
FieldDoc fillFields(final Entry entry) {
final int n = comparators.length;
final Comparable[] fields = new Comparable[n];
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);
}
/** 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 @@ public class FieldValueHitQueue extends PriorityQueue {
}
}
} 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 {

View File

@ -17,13 +17,15 @@ package org.apache.lucene.search;
* 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

View File

@ -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;
}
}

View File

@ -190,10 +190,14 @@ public class IndexSearcher extends Searcher {
}
/**
* 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,28 +226,31 @@ public class IndexSearcher extends Searcher {
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 {
search(weight, filter, new HitCollectorWrapper(results));
}
final MultiReaderHitCollector collector;
if (results instanceof MultiReaderHitCollector) {
collector = (MultiReaderHitCollector) results;
} else {
collector = new MultiReaderCollectorWrapper(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]);
@ -252,14 +259,14 @@ public class IndexSearcher extends Searcher {
}
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 @@ public class IndexSearcher extends Searcher {
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 @@ public class IndexSearcher extends Searcher {
} 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 class IndexSearcher extends Searcher {
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;
}
}
}

View File

@ -1,53 +0,0 @@
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;
/**
* Expert: A HitCollector that can be used to collect hits
* across sequential IndexReaders. For a Multi*Reader, this
* collector advances through each of the sub readers, in an
* arbitrary order. This results in a higher performance
* means of collection.
*
* <b>NOTE:</b> The doc that is passed to the collect method
* is relative to the current reader. You must re-base the
* doc, by recording the docBase from the last setNextReader
* call, to map it to the docID space of the
* Multi*Reader.
*
* <b>NOTE:</b> This API is experimental and might change in
* incompatible ways in the next release.
*/
public abstract class MultiReaderHitCollector extends HitCollector {
/**
* Called before collecting from each IndexReader. All doc
* ids in {@link #collect(int, float)} will correspond to reader.
*
* Add docBase to the current IndexReaders internal document id to
* re-base ids in {@link #collect(int, float)}.
*
* @param reader next IndexReader
* @param docBase
* @throws IOException
*/
public abstract void setNextReader(IndexReader reader, int docBase) throws IOException;
}

View File

@ -97,10 +97,15 @@ public class MultiSearcher extends Searcher {
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 @@ public class MultiSearcher extends Searcher {
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);
}

View File

@ -170,44 +170,51 @@ public class ParallelMultiSearcher extends MultiSearcher {
* @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)

View File

@ -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);
}
}

View File

@ -50,9 +50,12 @@ public class QueryWrapperFilter extends Filter {
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) {

View File

@ -45,12 +45,17 @@ public class RemoteSearchable
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();
}

View File

@ -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);
}
}

View File

@ -52,10 +52,20 @@ public abstract class Scorer extends DocIdSetIterator {
* @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.
* <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(doc(), score());
collector.collect(doc());
}
}
@ -66,10 +76,23 @@ public abstract class Scorer extends DocIdSetIterator {
* {@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.
* @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 (doc() < max) {
hc.collect(doc(), score());
collector.collect(doc());
if (!next())
return false;
}
@ -78,7 +101,8 @@ public abstract class Scorer extends DocIdSetIterator {
/** Returns the score of the current document matching the query.
* Initially invalid, until {@link #next()} or {@link #skipTo(int)}
* is called the first time.
* is called the first time, or when called from within
* {@link Collector#collect}.
*/
public abstract float score() throws IOException;

View File

@ -19,7 +19,7 @@ package org.apache.lucene.search;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexReader; // for javadoc
import org.apache.lucene.index.Term;
import org.apache.lucene.index.CorruptIndexException;
@ -51,10 +51,32 @@ public interface Searchable extends java.rmi.Remote {
* @param filter if non-null, used to permit documents to be collected.
* @param results to receive hits
* @throws BooleanQuery.TooManyClauses
* @deprecated use {@link #search(Weight, Filter, Collector)} instead.
*/
void search(Weight weight, Filter filter, HitCollector results)
throws IOException;
/**
* Lower-level search API.
*
* <p>
* {@link Collector#collect(int)} is called for every document. <br>
* Collector-based access to remote indexes is discouraged.
*
* <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, used to permit documents to be collected.
* @param collector
* to receive hits
* @throws BooleanQuery.TooManyClauses
*/
void search(Weight weight, Filter filter, Collector collector) throws IOException;
/** Frees resources associated with this Searcher.
* Be careful not to call this method while you are still using objects
@ -140,6 +162,7 @@ public interface Searchable extends java.rmi.Remote {
*/
Explanation explain(Weight weight, int doc) throws IOException;
// TODO: change the javadoc in 3.0 to remove the last NOTE section.
/** Expert: Low-level search implementation with arbitrary sorting. Finds
* the top <code>n</code> hits for <code>query</code>, applying
* <code>filter</code> if non-null, and sorting the hits by the criteria in
@ -147,6 +170,13 @@ public interface Searchable extends java.rmi.Remote {
*
* <p>Applications should usually call {@link
* Searcher#search(Query,Filter,Sort)} instead.
*
* <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.
*
* @throws BooleanQuery.TooManyClauses
*/
TopFieldDocs search(Weight weight, Filter filter, int n, Sort sort)

View File

@ -77,8 +77,12 @@ public abstract class Searcher implements Searchable {
* <code>filter</code> if non-null, and sorting the hits by the criteria in
* <code>sort</code>.
*
* <p>Applications should usually call {@link
* Searcher#search(Query,Filter,Sort)} instead.
* <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.
*
* @throws BooleanQuery.TooManyClauses
*/
public TopFieldDocs search(Query query, Filter filter, int n,
@ -99,12 +103,31 @@ public abstract class Searcher implements Searchable {
* In other words, the score will not necessarily be a float whose value is
* between 0 and 1.
* @throws BooleanQuery.TooManyClauses
* @deprecated use {@link #search(Query, Collector)} instead.
*/
public void search(Query query, HitCollector results)
throws IOException {
search(query, (Filter)null, 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.
* <p>Note: The <code>score</code> passed to this method is a raw score.
* In other words, the score will not necessarily be a float whose value is
* between 0 and 1.
* @throws BooleanQuery.TooManyClauses
*/
public void search(Query query, Collector results)
throws IOException {
search(query, (Filter)null, results);
}
/** Lower-level search API.
*
* <p>{@link HitCollector#collect(int,float)} is called for every matching
@ -120,12 +143,34 @@ public abstract class Searcher implements Searchable {
* @param filter if non-null, used to permit documents to be collected.
* @param results to receive hits
* @throws BooleanQuery.TooManyClauses
* @deprecated use {@link #search(Query, Filter, Collector)} instead.
*/
public void search(Query query, Filter filter, HitCollector results)
throws IOException {
search(createWeight(query), filter, results);
}
/** Lower-level search API.
*
* <p>{@link Collector#collect(int)} is called for every matching
* document.
* <br>Collector-based access to remote indexes is discouraged.
*
* <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, Filter, int)}) is usually more efficient, as it skips
* non-high-scoring hits.
*
* @param query to match documents
* @param filter if non-null, used to permit documents to be collected.
* @param results to receive hits
* @throws BooleanQuery.TooManyClauses
*/
public void search(Query query, Filter filter, Collector results)
throws IOException {
search(createWeight(query), filter, results);
}
/** Finds the top <code>n</code>
* hits for <code>query</code>, applying <code>filter</code> if non-null.
*
@ -197,7 +242,11 @@ public abstract class Searcher implements Searchable {
/* The following abstract methods were added as a workaround for GCJ bug #15411.
* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=15411
*/
/**
* @deprecated use {@link #search(Weight, Filter, Collector)} instead.
*/
abstract public void search(Weight weight, Filter filter, HitCollector results) throws IOException;
abstract public void search(Weight weight, Filter filter, Collector results) throws IOException;
abstract public void close() throws IOException;
abstract public int docFreq(Term term) throws IOException;
abstract public int maxDoc() throws IOException;

View File

@ -21,8 +21,6 @@ import java.io.IOException;
import java.io.Serializable;
import java.util.Locale;
import org.apache.lucene.index.IndexReader;
/**
* Stores information about how to sort documents by terms in an individual
* field. Fields must be indexed in order to sort by them.
@ -434,8 +432,6 @@ implements Serializable {
/** Returns the {@link FieldComparator} to use for sorting.
* @param subReaders array of {@link IndexReader} search
* will step through
* @param numHits number of top hits the queue will store
* @param sortPos position of this SortField within {@link
* Sort}. The comparator is primary if sortPos==0,
@ -444,7 +440,7 @@ implements Serializable {
* @param reversed True if the SortField is reversed
* @return {@link FieldComparator} to use when sorting
*/
protected FieldComparator getComparator(final IndexReader[] subReaders, final int numHits, final int sortPos, final boolean reversed) throws IOException {
protected FieldComparator getComparator(final int numHits, final int sortPos, final boolean reversed) throws IOException {
if (locale != null) {
// TODO: it'd be nice to allow FieldCache.getStringIndex
@ -480,7 +476,7 @@ implements Serializable {
case SortField.CUSTOM:
assert factory == null && comparatorSource != null;
return comparatorSource.newComparator(field, subReaders, numHits, sortPos, reversed);
return comparatorSource.newComparator(field, numHits, sortPos, reversed);
case SortField.STRING:
return new FieldComparator.StringOrdValComparator(numHits, field, sortPos, reversed);

View File

@ -24,6 +24,9 @@ import org.apache.lucene.index.TermDocs;
/** Expert: A <code>Scorer</code> for documents matching a <code>Term</code>.
*/
final class TermScorer extends Scorer {
private static final float[] SIM_NORM_DECODER = Similarity.getNormDecoder();
private Weight weight;
private TermDocs termDocs;
private byte[] norms;
@ -56,24 +59,25 @@ final class TermScorer extends Scorer {
scoreCache[i] = getSimilarity().tf(i) * weightValue;
}
/** @deprecated use {@link #score(Collector)} instead. */
public void score(HitCollector hc) throws IOException {
next();
score(hc, Integer.MAX_VALUE);
score(new HitCollectorWrapper(hc));
}
public void score(Collector c) throws IOException {
next();
score(c, Integer.MAX_VALUE);
}
/** @deprecated use {@link #score(Collector, int)} instead. */
protected boolean score(HitCollector c, int end) throws IOException {
Similarity similarity = getSimilarity(); // cache sim in local
float[] normDecoder = Similarity.getNormDecoder();
return score(new HitCollectorWrapper(c), end);
}
protected boolean score(Collector c, int end) throws IOException {
c.setScorer(this);
while (doc < end) { // for docs in window
int f = freqs[pointer];
float score = // compute tf(f)*weight
f < SCORE_CACHE_SIZE // check cache
? scoreCache[f] // cache hit
: similarity.tf(f)*weightValue; // cache miss
score *= normDecoder[norms[doc] & 0xFF]; // normalize for field
c.collect(doc, score); // collect score
c.collect(doc); // collect score
if (++pointer >= pointerMax) {
pointerMax = termDocs.read(docs, freqs); // refill buffers
@ -123,7 +127,7 @@ final class TermScorer extends Scorer {
? scoreCache[f] // cache hit
: getSimilarity().tf(f)*weightValue; // cache miss
return raw * Similarity.decodeNorm(norms[doc]); // normalize for field
return raw * SIM_NORM_DECODER[norms[doc] & 0xFF]; // normalize for field
}
/** Skips to the first match beyond the current whose document number is

View File

@ -1,7 +1,5 @@
package org.apache.lucene.search;
import org.apache.lucene.index.IndexReader;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@ -17,18 +15,20 @@ import org.apache.lucene.index.IndexReader;
* 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;
*/
/**
* <p>The TimeLimitedCollector is used to timeout search requests that
* take longer than the maximum allowed search time limit. After this
* time is exceeded, the search thread is stopped by throwing a
* TimeExceeded Exception.</p>
* <p>
* The TimeLimitedCollector is used to timeout search requests that take longer
* than the maximum allowed search time limit. After this time is exceeded, the
* search thread is stopped by throwing a TimeExceeded Exception.
* </p>
*
* @deprecated this class will be removed in 3.0. Use
* {@link TimeLimitingCollector} instead, which extends the new
* {@link Collector}.
*/
public class TimeLimitedCollector extends MultiReaderHitCollector {
public class TimeLimitedCollector extends HitCollector {
/**
* Default timer resolution.
@ -136,19 +136,15 @@ public class TimeLimitedCollector extends MultiReaderHitCollector {
private final long t0;
private final long timeout;
private final MultiReaderHitCollector hc;
private final HitCollector hc;
/**
* Create a TimeLimitedCollector wrapper over another HitCollector with a specified timeout.
* @param hc the wrapped HitCollector
* @param timeAllowed max time allowed for collecting hits after which {@link TimeExceededException} is thrown
*/
public TimeLimitedCollector( final HitCollector hc, final long timeAllowed ) {
if (hc instanceof MultiReaderHitCollector) {
this.hc = (MultiReaderHitCollector) hc;
} else {
this.hc = new IndexSearcher.MultiReaderCollectorWrapper(hc);
}
public TimeLimitedCollector(final HitCollector hc, final long timeAllowed) {
this.hc = hc;
t0 = TIMER_THREAD.getMilliseconds();
this.timeout = t0 + timeAllowed;
}
@ -219,7 +215,4 @@ public class TimeLimitedCollector extends MultiReaderHitCollector {
this.greedy = greedy;
}
public void setNextReader(IndexReader reader, int base) throws IOException {
hc.setNextReader(reader, base);
}
}

View File

@ -0,0 +1,219 @@
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;
/**
* The {@link TimeLimitingCollector} is used to timeout search requests that
* take longer than the maximum allowed search time limit. After this time is
* exceeded, the search thread is stopped by throwing a
* {@link TimeExceededException}.
*/
public class TimeLimitingCollector extends Collector {
/**
* Default timer resolution.
* @see #setResolution(long)
*/
public static final int DEFAULT_RESOLUTION = 20;
/**
* Default for {@link #isGreedy()}.
* @see #isGreedy()
*/
public boolean DEFAULT_GREEDY = false;
private static long resolution = DEFAULT_RESOLUTION;
private boolean greedy = DEFAULT_GREEDY ;
private static final class TimerThread extends Thread {
// NOTE: we can avoid explicit synchronization here for several reasons:
// * updates to volatile long variables are atomic
// * only single thread modifies this value
// * use of volatile keyword ensures that it does not reside in
// a register, but in main memory (so that changes are visible to
// other threads).
// * visibility of changes does not need to be instantanous, we can
// afford losing a tick or two.
//
// See section 17 of the Java Language Specification for details.
private volatile long time = 0;
/**
* TimerThread provides a pseudo-clock service to all searching
* threads, so that they can count elapsed time with less overhead
* than repeatedly calling System.currentTimeMillis. A single
* thread should be created to be used for all searches.
*/
private TimerThread() {
super("TimeLimitedCollector timer thread");
this.setDaemon( true );
}
public void run() {
while (true) {
// TODO: Use System.nanoTime() when Lucene moves to Java SE 5.
time += resolution;
try {
Thread.sleep( resolution );
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
/**
* Get the timer value in milliseconds.
*/
public long getMilliseconds() {
return time;
}
}
/** Thrown when elapsed search time exceeds allowed search time. */
public static class TimeExceededException extends RuntimeException {
private long timeAllowed;
private long timeElapsed;
private int lastDocCollected;
private TimeExceededException(long timeAllowed, long timeElapsed, int lastDocCollected) {
super("Elapsed time: " + timeElapsed + "Exceeded allowed search time: " + timeAllowed + " ms.");
this.timeAllowed = timeAllowed;
this.timeElapsed = timeElapsed;
this.lastDocCollected = lastDocCollected;
}
/** Returns allowed time (milliseconds). */
public long getTimeAllowed() {
return timeAllowed;
}
/** Returns elapsed time (milliseconds). */
public long getTimeElapsed() {
return timeElapsed;
}
/** Returns last doc that was collected when the search time exceeded. */
public int getLastDocCollected() {
return lastDocCollected;
}
}
// Declare and initialize a single static timer thread to be used by
// all TimeLimitedCollector instances. The JVM assures that
// this only happens once.
private final static TimerThread TIMER_THREAD = new TimerThread();
static {
TIMER_THREAD.start();
}
private final long t0;
private final long timeout;
private final Collector collector;
/**
* Create a TimeLimitedCollector wrapper over another {@link Collector} with a specified timeout.
* @param collector the wrapped {@link Collector}
* @param timeAllowed max time allowed for collecting hits after which {@link TimeExceededException} is thrown
*/
public TimeLimitingCollector(final Collector collector, final long timeAllowed ) {
this.collector = collector;
t0 = TIMER_THREAD.getMilliseconds();
this.timeout = t0 + timeAllowed;
}
/**
* Return the timer resolution.
* @see #setResolution(long)
*/
public static long getResolution() {
return resolution;
}
/**
* Set the timer resolution.
* The default timer resolution is 20 milliseconds.
* This means that a search required to take no longer than
* 800 milliseconds may be stopped after 780 to 820 milliseconds.
* <br>Note that:
* <ul>
* <li>Finer (smaller) resolution is more accurate but less efficient.</li>
* <li>Setting resolution to less than 5 milliseconds will be silently modified to 5 milliseconds.</li>
* <li>Setting resolution smaller than current resolution might take effect only after current
* resolution. (Assume current resolution of 20 milliseconds is modified to 5 milliseconds,
* then it can take up to 20 milliseconds for the change to have effect.</li>
* </ul>
*/
public static void setResolution(long newResolution) {
resolution = Math.max(newResolution,5); // 5 milliseconds is about the minimum reasonable time for a Object.wait(long) call.
}
/**
* Checks if this time limited collector is greedy in collecting the last hit.
* A non greedy collector, upon a timeout, would throw a {@link TimeExceededException}
* without allowing the wrapped collector to collect current doc. A greedy one would
* first allow the wrapped hit collector to collect current doc and only then
* throw a {@link TimeExceededException}.
* @see #setGreedy(boolean)
*/
public boolean isGreedy() {
return greedy;
}
/**
* Sets whether this time limited collector is greedy.
* @param greedy true to make this time limited greedy
* @see #isGreedy()
*/
public void setGreedy(boolean greedy) {
this.greedy = greedy;
}
/**
* Calls {@link Collector#collect(int)} on the decorated {@link Collector}
* unless the allowed time has passed, in which case it throws an exception.
*
* @throws TimeExceededException
* if the time allowed has exceeded.
*/
public void collect(final int doc) throws IOException {
long time = TIMER_THREAD.getMilliseconds();
if (timeout < time) {
if (greedy) {
//System.out.println(this+" greedy: before failing, collecting doc: "+doc+" "+(time-t0));
collector.collect(doc);
}
//System.out.println(this+" failing on: "+doc+" "+(time-t0));
throw new TimeExceededException( timeout-t0, time-t0, doc );
}
//System.out.println(this+" collecting: "+doc+" "+(time-t0));
collector.collect(doc);
}
public void setNextReader(IndexReader reader, int base) throws IOException {
collector.setNextReader(reader, base);
}
public void setScorer(Scorer scorer) throws IOException {
collector.setScorer(scorer);
}
}

View File

@ -29,7 +29,10 @@ public class TopDocs implements java.io.Serializable {
/** Expert: Stores the maximum score value encountered, needed for normalizing. */
private float maxScore;
/** Expert: Returns the maximum score value encountered. */
/**
* Expert: Returns the maximum score value encountered. Note that in case
* scores are not tracked, this returns {@link Float#NaN}.
*/
public float getMaxScore() {
return maxScore;
}
@ -39,6 +42,11 @@ public class TopDocs implements java.io.Serializable {
this.maxScore=maxScore;
}
/** Expert: Constructs a TopDocs with a default maxScore=Float.NaN. */
TopDocs(int totalHits, ScoreDoc[] scoreDocs) {
this(totalHits, scoreDocs, Float.NaN);
}
/** Expert: Constructs a TopDocs.*/
public TopDocs(int totalHits, ScoreDoc[] scoreDocs, float maxScore) {
this.totalHits = totalHits;

View File

@ -0,0 +1,138 @@
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 org.apache.lucene.util.PriorityQueue;
/**
* A base class for all collectors that return a {@link TopDocs} output. This
* collector allows easy extension by providing a single constructor which
* accepts a {@link PriorityQueue} as well as protected members for that
* priority queue and a counter of the number of total hits.<br>
* Extending classes can override {@link #topDocs(int, int)} and
* {@link #getTotalHits()} in order to provide their own implementation.
*/
public abstract class TopDocsCollector extends Collector {
// This is used in case topDocs() is called with illegal parameters, or there
// simply aren't (enough) results.
protected static final TopDocs EMPTY_TOPDOCS = new TopDocs(0, new ScoreDoc[0], Float.NaN);
/**
* The priority queue which holds the top documents. Note that different
* implementations of PriorityQueue give different meaning to 'top documents'.
* HitQueue for example aggregates the top scoring documents, while other PQ
* implementations may hold documents sorted by other criteria.
*/
protected PriorityQueue pq;
/** The total number of documents that the collector encountered. */
protected int totalHits;
protected TopDocsCollector(PriorityQueue pq) {
this.pq = pq;
}
/**
* Populates the results array with the ScoreDoc instaces. This can be
* overridden in case a different ScoreDoc type should be returned.
*/
protected void populateResults(ScoreDoc[] results, int howMany) {
for (int i = howMany - 1; i >= 0; i--) {
results[i] = (ScoreDoc) pq.pop();
}
}
/**
* Returns a {@link TopDocs} instance containing the given results. If
* <code>results</code> is null it means there are no results to return,
* either because there were 0 calls to collect() or because the arguments to
* topDocs were invalid.
*/
protected TopDocs newTopDocs(ScoreDoc[] results, int start) {
return results == null ? EMPTY_TOPDOCS : new TopDocs(totalHits, results);
}
/** The total number of documents that matched this query. */
public int getTotalHits() {
return totalHits;
}
/** Returns the top docs that were collected by this collector. */
public final TopDocs topDocs() {
return topDocs(0, pq.size());
}
/**
* Returns the documents in the rage [start .. pq.size()) that were collected
* by this collector. Note that if start >= pq.size(), an empty TopDocs is
* returned.<br>
* This method is convenient to call if the application allways asks for the
* last results, starting from the last 'page'.<br>
* <b>NOTE:</b> you cannot call this method more than once for each search
* execution. If you need to call it more than once, passing each time a
* different <code>start</code>, you should call {@link #topDocs()} and work
* with the returned {@link TopDocs} object, which will contain all the
* results this search execution collected.
*/
public final TopDocs topDocs(int start) {
return topDocs(start, pq.size());
}
/**
* Returns the documents in the rage [start .. start+howMany) that were
* collected by this collector. Note that if start >= pq.size(), an empty
* TopDocs is returned, and if pq.size() - start &lt; howMany, then only the
* available documents in [start .. pq.size()) are returned.<br>
* This method is useful to call in case pagination of search results is
* allowed by the search application, as well as it attempts to optimize the
* memory used by allocating only as much as requested by howMany.<br>
* <b>NOTE:</b> you cannot call this method more than once for each search
* execution. If you need to call it more than once, passing each time a
* different range, you should call {@link #topDocs()} and work with the
* returned {@link TopDocs} object, which will contain all the results this
* search execution collected.
*/
public TopDocs topDocs(int start, int howMany) {
int pqsize = pq.size();
// Don't bother to throw an exception, just return an empty TopDocs in case
// the parameters are invalid or out of range.
if (start < 0 || start >= pqsize || howMany <= 0) {
return newTopDocs(null, start);
}
// We know that start < pqsize, so just fix howMany.
howMany = Math.min(pqsize - start, howMany);
ScoreDoc[] results = new ScoreDoc[howMany];
// pq's pop() returns the 'least' element in the queue, therefore need
// to discard the first ones, until we reach the requested range.
// Note that this loop will usually not be executed, since the common usage
// should be that the caller asks for the last howMany results. However it's
// needed here for completeness.
for (int i = pqsize - start - howMany; i > 0; i--) { pq.pop(); }
// Get the requested results from pq.
populateResults(results, howMany);
return newTopDocs(results, start);
}
}

View File

@ -21,201 +21,569 @@ import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldValueHitQueue.Entry;
import org.apache.lucene.util.PriorityQueue;
/**
* A {@link HitCollector} that sorts by {@link SortField} using
* A {@link Collector} that sorts by {@link SortField} using
* {@link FieldComparator}s.
*
* <b>NOTE:</b> This API is experimental and might change in
* incompatible ways in the next release.
* <p><b>NOTE:</b> This API is experimental and might change in
* incompatible ways in the next release.</p>
*/
public final class TopFieldCollector extends MultiReaderHitCollector {
public abstract class TopFieldCollector extends TopDocsCollector {
private final FieldValueHitQueue queue;
// TODO: one optimization we could do is to pre-fill
// the queue with sentinel value that guaranteed to
// always compare lower than a real hit; this would
// save having to check queueFull on each insert
private final FieldComparator[] comparators;
private FieldComparator comparator1;
private final int numComparators;
private int[] reverseMul;
private int reverseMul1 = 0;
/*
* Implements a TopFieldCollector over one SortField criteria, without
* tracking document scores and maxScore.
*/
private static class OneComparatorNonScoringCollector extends
TopFieldCollector {
private final int numHits;
private int totalHits;
private FieldValueHitQueue.Entry bottom = null;
final FieldComparator comparator;
final int reverseMul;
/** Stores the maximum score value encountered, needed for normalizing. */
private float maxScore = Float.NEGATIVE_INFINITY;
private boolean queueFull;
private boolean fillFields;
public TopFieldCollector(Sort sort, int numHits, IndexReader[] subReaders, boolean fillFields)
throws IOException {
if (sort.fields.length == 0) {
throw new IllegalArgumentException("Sort must contain at least one field");
public OneComparatorNonScoringCollector(FieldValueHitQueue queue,
int numHits, boolean fillFields) throws IOException {
super(queue, numHits, fillFields);
comparator = queue.getComparators()[0];
reverseMul = queue.getReverseMul()[0];
}
queue = new FieldValueHitQueue(sort.fields, numHits, subReaders);
comparators = queue.getComparators();
reverseMul = queue.getReverseMul();
numComparators = comparators.length;
if (numComparators == 1) {
comparator1 = comparators[0];
reverseMul1 = reverseMul[0];
} else {
comparator1 = null;
reverseMul1 = 0;
private final void updateBottom(int doc) {
// bottom.score is already set to Float.NaN in add().
bottom.docID = docBase + doc;
pq.adjustTop();
bottom = (FieldValueHitQueue.Entry) pq.top();
}
public void collect(int doc) throws IOException {
++totalHits;
if (queueFull) {
// Fastmatch: return if this hit is not competitive
final int cmp = reverseMul * comparator.compareBottom(doc);
if (cmp < 0 || (cmp == 0 && doc + docBase > bottom.docID)) {
return;
}
// This hit is competitive - replace bottom element in queue & adjustTop
comparator.copy(bottom.slot, doc);
updateBottom(doc);
comparator.setBottom(bottom.slot);
} else {
// Startup transient: queue hasn't gathered numHits yet
final int slot = totalHits - 1;
// Copy hit into queue
comparator.copy(slot, doc);
add(slot, doc, Float.NaN);
if (queueFull) {
comparator.setBottom(bottom.slot);
}
}
}
public void setNextReader(IndexReader reader, int docBase) throws IOException {
final int numSlotsFull = queueFull ? numHits : totalHits;
this.docBase = docBase;
comparator.setNextReader(reader, docBase, numSlotsFull);
}
public void setScorer(Scorer scorer) throws IOException {
comparator.setScorer(scorer);
}
}
/*
* Implements a TopFieldCollector over one SortField criteria, while tracking
* document scores but no maxScore.
*/
private static class OneComparatorScoringNoMaxScoreCollector extends
OneComparatorNonScoringCollector {
private Scorer scorer;
public OneComparatorScoringNoMaxScoreCollector(FieldValueHitQueue queue,
int numHits, boolean fillFields) throws IOException {
super(queue, numHits, fillFields);
}
private final void updateBottom(int doc, float score) {
bottom.docID = docBase + doc;
bottom.score = score;
pq.adjustTop();
bottom = (FieldValueHitQueue.Entry) pq.top();
}
public void collect(int doc) throws IOException {
++totalHits;
if (queueFull) {
// Fastmatch: return if this hit is not competitive
final int cmp = reverseMul * comparator.compareBottom(doc);
if (cmp < 0 || (cmp == 0 && doc + docBase > bottom.docID)) {
return;
}
// Compute the score only if the hit is competitive.
final float score = scorer.score();
// This hit is competitive - replace bottom element in queue & adjustTop
comparator.copy(bottom.slot, doc);
updateBottom(doc, score);
comparator.setBottom(bottom.slot);
} else {
// Compute the score only if the hit is competitive.
final float score = scorer.score();
// Startup transient: queue hasn't gathered numHits yet
final int slot = totalHits - 1;
// Copy hit into queue
comparator.copy(slot, doc);
add(slot, doc, score);
if (queueFull) {
comparator.setBottom(bottom.slot);
}
}
}
public void setNextReader(IndexReader reader, int docBase) throws IOException {
final int numSlotsFull = queueFull ? numHits : totalHits;
this.docBase = docBase;
comparator.setNextReader(reader, docBase, numSlotsFull);
}
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
comparator.setScorer(scorer);
}
}
/*
* Implements a TopFieldCollector over one SortField criteria, with tracking
* document scores and maxScore.
*/
private final static class OneComparatorScoringMaxScoreCollector extends
OneComparatorNonScoringCollector {
private Scorer scorer;
public OneComparatorScoringMaxScoreCollector(FieldValueHitQueue queue,
int numHits, boolean fillFields) throws IOException {
super(queue, numHits, fillFields);
// Must set maxScore to NEG_INF, or otherwise Math.max always returns NaN.
maxScore = Float.NEGATIVE_INFINITY;
}
private final void updateBottom(int doc, float score) {
bottom.docID = docBase + doc;
bottom.score = score;
pq.adjustTop();
bottom = (FieldValueHitQueue.Entry) pq.top();
}
public void collect(int doc) throws IOException {
final float score = scorer.score();
if (score > maxScore) {
maxScore = score;
}
++totalHits;
if (queueFull) {
// Fastmatch: return if this hit is not competitive
final int cmp = reverseMul * comparator.compareBottom(doc);
if (cmp < 0 || (cmp == 0 && doc + docBase > bottom.docID)) {
return;
}
// This hit is competitive - replace bottom element in queue & adjustTop
comparator.copy(bottom.slot, doc);
updateBottom(doc, score);
comparator.setBottom(bottom.slot);
} else {
// Startup transient: queue hasn't gathered numHits yet
final int slot = totalHits - 1;
// Copy hit into queue
comparator.copy(slot, doc);
add(slot, doc, score);
if (queueFull) {
comparator.setBottom(bottom.slot);
}
}
}
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
super.setScorer(scorer);
}
}
/*
* Implements a TopFieldCollector over multiple SortField criteria, without
* tracking document scores and maxScore.
*/
private static class MultiComparatorNonScoringCollector extends TopFieldCollector {
final FieldComparator[] comparators;
final int[] reverseMul;
public MultiComparatorNonScoringCollector(FieldValueHitQueue queue,
int numHits, boolean fillFields) throws IOException {
super(queue, numHits, fillFields);
comparators = queue.getComparators();
reverseMul = queue.getReverseMul();
}
private final void updateBottom(int doc) {
// bottom.score is already set to Float.NaN in add().
bottom.docID = docBase + doc;
pq.adjustTop();
bottom = (FieldValueHitQueue.Entry) pq.top();
}
public void collect(int doc) throws IOException {
++totalHits;
if (queueFull) {
// Fastmatch: return if this hit is not competitive
for (int i = 0;; i++) {
final int c = reverseMul[i] * comparators[i].compareBottom(doc);
if (c < 0) {
// Definitely not competitive
return;
} else if (c > 0) {
// Definitely competitive
break;
} else if (i == comparators.length - 1) {
// This is the equals case.
if (doc + docBase > bottom.docID) {
// Definitely not competitive
return;
}
break;
}
}
// This hit is competitive - replace bottom element in queue & adjustTop
for (int i = 0; i < comparators.length; i++) {
comparators[i].copy(bottom.slot, doc);
}
updateBottom(doc);
for (int i = 0; i < comparators.length; i++) {
comparators[i].setBottom(bottom.slot);
}
} else {
// Startup transient: queue hasn't gathered numHits yet
final int slot = totalHits - 1;
// Copy hit into queue
for (int i = 0; i < comparators.length; i++) {
comparators[i].copy(slot, doc);
}
add(slot, doc, Float.NaN);
if (queueFull) {
for (int i = 0; i < comparators.length; i++) {
comparators[i].setBottom(bottom.slot);
}
}
}
}
public void setNextReader(IndexReader reader, int docBase) throws IOException {
final int numSlotsFull = queueFull ? numHits : totalHits;
this.docBase = docBase;
for (int i = 0; i < comparators.length; i++) {
comparators[i].setNextReader(reader, docBase, numSlotsFull);
}
}
public void setScorer(Scorer scorer) throws IOException {
// set the scorer on all comparators
for (int i = 0; i < comparators.length; i++) {
comparators[i].setScorer(scorer);
}
}
}
/*
* Implements a TopFieldCollector over multiple SortField criteria, with
* tracking document scores and maxScore.
*/
private final static class MultiComparatorScoringMaxScoreCollector extends MultiComparatorNonScoringCollector {
private Scorer scorer;
public MultiComparatorScoringMaxScoreCollector(FieldValueHitQueue queue,
int numHits, boolean fillFields) throws IOException {
super(queue, numHits, fillFields);
// Must set maxScore to NEG_INF, or otherwise Math.max always returns NaN.
maxScore = Float.NEGATIVE_INFINITY;
}
private final void updateBottom(int doc, float score) {
bottom.docID = docBase + doc;
bottom.score = score;
pq.adjustTop();
bottom = (FieldValueHitQueue.Entry) pq.top();
}
public void collect(int doc) throws IOException {
final float score = scorer.score();
if (score > maxScore) {
maxScore = score;
}
++totalHits;
if (queueFull) {
// Fastmatch: return if this hit is not competitive
for (int i = 0;; i++) {
final int c = reverseMul[i] * comparators[i].compareBottom(doc);
if (c < 0) {
// Definitely not competitive
return;
} else if (c > 0) {
// Definitely competitive
break;
} else if (i == comparators.length - 1) {
// This is the equals case.
if (doc + docBase > bottom.docID) {
// Definitely not competitive
return;
}
break;
}
}
// This hit is competitive - replace bottom element in queue & adjustTop
for (int i = 0; i < comparators.length; i++) {
comparators[i].copy(bottom.slot, doc);
}
updateBottom(doc, score);
for (int i = 0; i < comparators.length; i++) {
comparators[i].setBottom(bottom.slot);
}
} else {
// Startup transient: queue hasn't gathered numHits yet
final int slot = totalHits - 1;
// Copy hit into queue
for (int i = 0; i < comparators.length; i++) {
comparators[i].copy(slot, doc);
}
add(slot, doc, score);
if (queueFull) {
for (int i = 0; i < comparators.length; i++) {
comparators[i].setBottom(bottom.slot);
}
}
}
}
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
super.setScorer(scorer);
}
}
/*
* Implements a TopFieldCollector over multiple SortField criteria, with
* tracking document scores and maxScore.
*/
private final static class MultiComparatorScoringNoMaxScoreCollector extends MultiComparatorNonScoringCollector {
private Scorer scorer;
public MultiComparatorScoringNoMaxScoreCollector(FieldValueHitQueue queue,
int numHits, boolean fillFields) throws IOException {
super(queue, numHits, fillFields);
}
private final void updateBottom(int doc, float score) {
bottom.docID = docBase + doc;
bottom.score = score;
pq.adjustTop();
bottom = (FieldValueHitQueue.Entry) pq.top();
}
public void collect(int doc) throws IOException {
++totalHits;
if (queueFull) {
// Fastmatch: return if this hit is not competitive
for (int i = 0;; i++) {
final int c = reverseMul[i] * comparators[i].compareBottom(doc);
if (c < 0) {
// Definitely not competitive
return;
} else if (c > 0) {
// Definitely competitive
break;
} else if (i == comparators.length - 1) {
// This is the equals case.
if (doc + docBase > bottom.docID) {
// Definitely not competitive
return;
}
break;
}
}
// This hit is competitive - replace bottom element in queue & adjustTop
for (int i = 0; i < comparators.length; i++) {
comparators[i].copy(bottom.slot, doc);
}
// Compute score only if it is competitive.
final float score = scorer.score();
updateBottom(doc, score);
for (int i = 0; i < comparators.length; i++) {
comparators[i].setBottom(bottom.slot);
}
} else {
// Startup transient: queue hasn't gathered numHits yet
final int slot = totalHits - 1;
// Copy hit into queue
for (int i = 0; i < comparators.length; i++) {
comparators[i].copy(slot, doc);
}
// Compute score only if it competitive.
final float score = scorer.score();
add(slot, doc, score);
if (queueFull) {
for (int i = 0; i < comparators.length; i++) {
comparators[i].setBottom(bottom.slot);
}
}
}
}
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
super.setScorer(scorer);
}
}
private static final ScoreDoc[] EMPTY_SCOREDOCS = new ScoreDoc[0];
private final boolean fillFields;
/*
* Stores the maximum score value encountered, needed for normalizing. If
* document scores are not tracked, this value is initialized to NaN.
*/
float maxScore = Float.NaN;
final int numHits;
FieldValueHitQueue.Entry bottom = null;
boolean queueFull;
int docBase;
// Declaring the constructor private prevents extending this class by anyone
// else. Note that the class cannot be final since it's extended by the
// internal versions. If someone will define a constructor with any other
// visibility, then anyone will be able to extend the class, which is not what
// we want.
private TopFieldCollector(PriorityQueue pq, int numHits, boolean fillFields) {
super(pq);
this.numHits = numHits;
this.fillFields = fillFields;
}
int currentDocBase;
/**
* Creates a new {@link TopFieldCollector} from the given arguments.
*
* @param sort
* the sort criteria (SortFields).
* @param numHits
* the number of results to collect.
* @param fillFields
* specifies whether the actual field values should be returned on
* the results (FieldDoc).
* @param trackDocScores
* specifies whether document scores should be tracked and set on the
* results. Note that if set to false, then the results' scores will
* be set to Float.NaN. Setting this to true affects performance, as
* it incurs the score computation on each competitive result.
* Therefore if document scores are not required by the application,
* it is recommended to set it to false.
* @param trackMaxScore
* specifies whether the query's maxScore should be tracked and set
* on the resulting {@link TopDocs}. Note that if set to false,
* {@link TopDocs#getMaxScore()} returns Float.NaN. Setting this to
* true affects performance as it incurs the score computation on
* each result. Also, setting this true automatically sets
* <code>trackDocScores</code> to true as well.
* @return a {@link TopFieldCollector} instance which will sort the results by
* the sort criteria.
* @throws IOException
*/
public static TopFieldCollector create(Sort sort, int numHits,
boolean fillFields, boolean trackDocScores, boolean trackMaxScore)
throws IOException {
if (sort.fields.length == 0) {
throw new IllegalArgumentException("Sort must contain at least one field");
}
// javadoc inherited
public void setNextReader(IndexReader reader, int docBase) throws IOException {
final int numSlotsFull;
if (queueFull)
numSlotsFull = numHits;
else
numSlotsFull = totalHits;
FieldValueHitQueue queue = FieldValueHitQueue.create(sort.fields, numHits);
if (queue.getComparators().length == 1) {
if (trackMaxScore) {
return new OneComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
} else if (trackDocScores) {
return new OneComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
} else {
return new OneComparatorNonScoringCollector(queue, numHits, fillFields);
}
}
currentDocBase = docBase;
for (int i = 0; i < numComparators; i++) {
comparators[i].setNextReader(reader, docBase, numSlotsFull);
// multiple comparators.
if (trackMaxScore) {
return new MultiComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
} else if (trackDocScores) {
return new MultiComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
} else {
return new MultiComparatorNonScoringCollector(queue, numHits, fillFields);
}
}
private final void updateBottom(int doc, float score) {
bottom.docID = currentDocBase + doc;
bottom.score = score;
queue.adjustTop();
bottom = (FieldValueHitQueue.Entry) queue.top();
}
private final void add(int slot, int doc, float score) {
queue.put(new FieldValueHitQueue.Entry(slot, currentDocBase+doc, score));
bottom = (FieldValueHitQueue.Entry) queue.top();
final void add(int slot, int doc, float score) {
pq.put(new FieldValueHitQueue.Entry(slot, docBase + doc, score));
bottom = (FieldValueHitQueue.Entry) pq.top();
queueFull = totalHits == numHits;
}
// javadoc inherited
public void collect(int doc, float score) {
if (score > 0.0f) {
/*
* Only the following callback methods need to be overridden since
* topDocs(int, int) calls them to return the results.
*/
maxScore = Math.max(maxScore, score);
totalHits++;
// TODO: one optimization we could do is to pre-fill
// the queue with sentinel value that guaranteed to
// always compare lower than a real hit; this would
// save having to check queueFull on each insert
if (queueFull) {
if (numComparators == 1) {
// Common case
// Fastmatch: return if this hit is not competitive
final int cmp = reverseMul1 * comparator1.compareBottom(doc, score);
if (cmp < 0) {
// Definitely not competitive
return;
} else if (cmp == 0 && doc + currentDocBase > bottom.docID) {
// Definitely not competitive
return;
}
// This hit is competitive -- replace bottom
// element in queue & adjustTop
comparator1.copy(bottom.slot, doc, score);
updateBottom(doc, score);
comparator1.setBottom(bottom.slot);
} else {
// Fastmatch: return if this hit is not competitive
for(int i=0;;i++) {
final int c = reverseMul[i] * comparators[i].compareBottom(doc, score);
if (c < 0) {
// Definitely not competitive
return;
} else if (c > 0) {
// Definitely competitive
break;
} else if (i == numComparators-1) {
// This is the equals case.
if (doc + currentDocBase > bottom.docID) {
// Definitely not competitive
return;
} else {
break;
}
}
}
// This hit is competitive -- replace bottom
// element in queue & adjustTop
for (int i = 0; i < numComparators; i++) {
comparators[i].copy(bottom.slot, doc, score);
}
updateBottom(doc, score);
for(int i=0;i<numComparators;i++) {
comparators[i].setBottom(bottom.slot);
}
}
} else {
// Startup transient: queue hasn't gathered numHits
// yet
final int slot = totalHits-1;
// Copy hit into queue
if (numComparators == 1) {
// Common case
comparator1.copy(slot, doc, score);
add(slot, doc, score);
if (queueFull) {
comparator1.setBottom(bottom.slot);
}
} else {
for (int i = 0; i < numComparators; i++) {
comparators[i].copy(slot, doc, score);
}
add(slot, doc, score);
if (queueFull) {
for(int i=0;i<numComparators;i++) {
comparators[i].setBottom(bottom.slot);
}
}
}
}
}
}
// javadoc inherited
public TopDocs topDocs() {
ScoreDoc[] scoreDocs = new ScoreDoc[queue.size()];
protected void populateResults(ScoreDoc[] results, int howMany) {
FieldValueHitQueue queue = (FieldValueHitQueue) pq;
if (fillFields) {
for (int i = queue.size() - 1; i >= 0; i--) {
scoreDocs[i] = queue.fillFields((FieldValueHitQueue.Entry) queue.pop());
results[i] = queue.fillFields((FieldValueHitQueue.Entry) queue.pop());
}
} else {
Entry entry = (FieldValueHitQueue.Entry) queue.pop();
for (int i = queue.size() - 1; i >= 0; i--) {
scoreDocs[i] = new FieldDoc(entry.docID,
entry.score);
Entry entry = (FieldValueHitQueue.Entry) queue.pop();
results[i] = new FieldDoc(entry.docID, entry.score);
}
}
return new TopFieldDocs(totalHits, scoreDocs, queue.getFields(), maxScore);
}
protected TopDocs newTopDocs(ScoreDoc[] results, int start) {
if (results == null) {
results = EMPTY_SCOREDOCS;
// Set maxScore to NaN, in case this is a maxScore tracking collector.
maxScore = Float.NaN;
}
// If this is a maxScoring tracking collector and there were no results,
return new TopFieldDocs(totalHits, results, ((FieldValueHitQueue) pq).getFields(), maxScore);
}
}

View File

@ -30,8 +30,8 @@ import org.apache.lucene.index.IndexReader;
* documents are collected.
*
* @deprecated Please use {@link TopFieldCollector} instead.
**/
public class TopFieldDocCollector extends TopScoreDocCollector {
*/
public class TopFieldDocCollector extends TopDocCollector {
private FieldDoc reusableFD;
@ -50,7 +50,7 @@ public class TopFieldDocCollector extends TopScoreDocCollector {
if (score > 0.0f) {
totalHits++;
if (reusableFD == null)
reusableFD = new FieldDoc(doc + docBase, score);
reusableFD = new FieldDoc(doc, score);
else {
// Whereas TopScoreDocCollector can skip this if the
// score is not competitive, we cannot because the
@ -58,7 +58,7 @@ public class TopFieldDocCollector extends TopScoreDocCollector {
// aren't in general congruent with "higher score
// wins"
reusableFD.score = score;
reusableFD.doc = doc + docBase;
reusableFD.doc = doc;
}
reusableFD = (FieldDoc) hq.insertWithOverflow(reusableFD);
}

View File

@ -17,85 +17,74 @@ package org.apache.lucene.search;
* limitations under the License.
*/
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.util.PriorityQueue;
import java.io.IOException;
/** A {@link MultiReaderHitCollector} implementation that
* collects the top-scoring documents, returning them as a
* {@link TopDocs}. This is used by {@link IndexSearcher}
* to implement {@link TopDocs}-based search.
*
* <p>This may be extended, overriding the {@link
* MultiReaderHitCollector#collect} method to, e.g.,
* conditionally invoke <code>super()</code> in order to
* filter which documents are collected, but sure you
* either take docBase into account, or also override
* {@link MultiReaderHitCollector#setNextReader} method. */
public class TopScoreDocCollector extends MultiReaderHitCollector {
import org.apache.lucene.index.IndexReader;
/**
* A {@link Collector} implementation that collects the
* top-scoring hits, returning them as a {@link
* TopDocs}. This is used by {@link IndexSearcher} to
* implement {@link TopDocs}-based search. Hits are sorted
* by score descending and then (when the scores are tied)
* docID ascending.
*/
public final class TopScoreDocCollector extends TopDocsCollector {
private ScoreDoc reusableSD;
/** The total number of hits the collector encountered. */
protected int totalHits;
/** The priority queue which holds the top-scoring documents. */
protected PriorityQueue hq;
protected int docBase = 0;
private int docBase = 0;
private Scorer scorer;
/** Construct to collect a given number of hits.
* @param numHits the maximum number of hits to collect
*/
public TopScoreDocCollector(int numHits) {
this(new HitQueue(numHits));
super(new HitQueue(numHits));
}
/** Constructor to collect the top-scoring documents by using the given PQ.
* @param hq the PQ to use by this instance.
*/
protected TopScoreDocCollector(PriorityQueue hq) {
this.hq = hq;
protected TopDocs newTopDocs(ScoreDoc[] results, int start) {
if (results == null) {
return EMPTY_TOPDOCS;
}
// We need to compute maxScore in order to set it in TopDocs. If start == 0,
// it means the largest element is already in results, use its score as
// maxScore. Otherwise pop everything else, until the largest element is
// extracted and use its score as maxScore.
float maxScore = Float.NaN;
if (start == 0) {
maxScore = results[0].score;
} else {
for (int i = pq.size(); i > 1; i--) { pq.pop(); }
maxScore = ((ScoreDoc) pq.pop()).score;
}
return new TopDocs(totalHits, results, maxScore);
}
// javadoc inherited
public void collect(int doc, float score) {
if (score > 0.0f) {
totalHits++;
if (reusableSD == null) {
reusableSD = new ScoreDoc(doc + docBase, score);
} else if (score >= reusableSD.score) {
// reusableSD holds the last "rejected" entry, so, if
// this new score is not better than that, there's no
// need to try inserting it
reusableSD.doc = doc + docBase;
reusableSD.score = score;
} else {
return;
}
reusableSD = (ScoreDoc) hq.insertWithOverflow(reusableSD);
public void collect(int doc) throws IOException {
float score = scorer.score();
totalHits++;
if (reusableSD == null) {
reusableSD = new ScoreDoc(doc + docBase, score);
} else if (score >= reusableSD.score) {
// reusableSD holds the last "rejected" entry, so, if
// this new score is not better than that, there's no
// need to try inserting it
reusableSD.doc = doc + docBase;
reusableSD.score = score;
} else {
return;
}
}
/** The total number of documents that matched this query. */
public int getTotalHits() {
return totalHits;
}
/** The top-scoring hits. */
public TopDocs topDocs() {
ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()];
for (int i = hq.size()-1; i >= 0; i--) { // put docs in array
scoreDocs[i] = (ScoreDoc) hq.pop();
}
float maxScore = (totalHits==0)
? Float.NEGATIVE_INFINITY
: scoreDocs[0].score;
return new TopDocs(totalHits, scoreDocs, maxScore);
reusableSD = (ScoreDoc) pq.insertWithOverflow(reusableSD);
}
public void setNextReader(IndexReader reader, int base) {
docBase = base;
}
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
}

View File

@ -23,10 +23,10 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import junit.framework.TestSuite;
import junit.textui.TestRunner;
@ -35,15 +35,16 @@ import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.document.SetBasedFieldSelector;
import org.apache.lucene.index.IndexReader.FieldOption;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiReaderHitCollector;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory;
@ -1651,7 +1652,7 @@ public class TestIndexReader extends LuceneTestCase
Query q = new TermQuery(new Term("id", "a"));
IndexSearcher s = new IndexSearcher(dir);
s.search(q, new MultiReaderHitCollector() {
s.search(q, new Collector() {
int lastDocBase = -1;
public void setNextReader(IndexReader reader, int docBase) {
if (lastDocBase == -1) {
@ -1663,13 +1664,14 @@ public class TestIndexReader extends LuceneTestCase
}
lastDocBase = docBase;
}
public void collect(int doc, float score) {}
public void collect(int doc) {}
public void setScorer(Scorer scorer) {}
});
s.close();
IndexReader r = IndexReader.open(dir);
s = new IndexSearcher(r, true);
s.search(q, new MultiReaderHitCollector() {
s.search(q, new Collector() {
int lastDocBase = -1;
public void setNextReader(IndexReader reader, int docBase) {
if (lastDocBase == -1) {
@ -1681,7 +1683,8 @@ public class TestIndexReader extends LuceneTestCase
}
lastDocBase = docBase;
}
public void collect(int doc, float score) {}
public void collect(int doc) {}
public void setScorer(Scorer scorer) {}
});
s.close();
r.close();

View File

@ -17,6 +17,7 @@ package org.apache.lucene.index;
* limitations under the License.
*/
import java.io.IOException;
import java.util.Collection;
import org.apache.lucene.util.LuceneTestCase;
@ -27,7 +28,8 @@ import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiReaderHitCollector;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.TermQuery;
@ -283,10 +285,15 @@ public class TestOmitTf extends LuceneTestCase {
searcher.search(q1,
new CountingHitCollector() {
public final void collect(int doc, float score) {
private Scorer scorer;
public final void setScorer(Scorer scorer) {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
//System.out.println("Q1: Doc=" + doc + " score=" + score);
float score = scorer.score();
assertTrue(score==1.0f);
super.collect(doc, score);
super.collect(doc);
}
});
//System.out.println(CountingHitCollector.getCount());
@ -294,10 +301,15 @@ public class TestOmitTf extends LuceneTestCase {
searcher.search(q2,
new CountingHitCollector() {
public final void collect(int doc, float score) {
private Scorer scorer;
public final void setScorer(Scorer scorer) {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
//System.out.println("Q2: Doc=" + doc + " score=" + score);
float score = scorer.score();
assertTrue(score==1.0f+doc);
super.collect(doc, score);
super.collect(doc);
}
});
//System.out.println(CountingHitCollector.getCount());
@ -308,11 +320,16 @@ public class TestOmitTf extends LuceneTestCase {
searcher.search(q3,
new CountingHitCollector() {
public final void collect(int doc, float score) {
private Scorer scorer;
public final void setScorer(Scorer scorer) {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
//System.out.println("Q1: Doc=" + doc + " score=" + score);
float score = scorer.score();
assertTrue(score==1.0f);
assertFalse(doc%2==0);
super.collect(doc, score);
super.collect(doc);
}
});
//System.out.println(CountingHitCollector.getCount());
@ -320,11 +337,16 @@ public class TestOmitTf extends LuceneTestCase {
searcher.search(q4,
new CountingHitCollector() {
public final void collect(int doc, float score) {
private Scorer scorer;
public final void setScorer(Scorer scorer) {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
float score = scorer.score();
//System.out.println("Q1: Doc=" + doc + " score=" + score);
assertTrue(score==1.0f);
assertTrue(doc%2==0);
super.collect(doc, score);
super.collect(doc);
}
});
//System.out.println(CountingHitCollector.getCount());
@ -337,9 +359,9 @@ public class TestOmitTf extends LuceneTestCase {
searcher.search(bq,
new CountingHitCollector() {
public final void collect(int doc, float score) {
public final void collect(int doc) throws IOException {
//System.out.println("BQ: Doc=" + doc + " score=" + score);
super.collect(doc, score);
super.collect(doc);
}
});
assertTrue(15 == CountingHitCollector.getCount());
@ -348,12 +370,13 @@ public class TestOmitTf extends LuceneTestCase {
dir.close();
}
public static class CountingHitCollector extends MultiReaderHitCollector {
public static class CountingHitCollector extends Collector {
static int count=0;
static int sum=0;
private int docBase = -1;
CountingHitCollector(){count=0;sum=0;}
public void collect(int doc, float score) {
public void setScorer(Scorer scorer) throws IOException {}
public void collect(int doc) throws IOException {
count++;
sum += doc + docBase; // use it to avoid any possibility of being optimized away
}

View File

@ -89,9 +89,10 @@ public class CheckHits {
}
final Set actual = new TreeSet();
searcher.search(query, new MultiReaderHitCollector() {
private int base = -1;
public void collect(int doc, float score) {
searcher.search(query, new Collector() {
private int base = 0;
public void setScorer(Scorer scorer) throws IOException {}
public void collect(int doc) {
actual.add(new Integer(doc + base));
}
@ -390,14 +391,22 @@ public class CheckHits {
checkExplanations(query);
return super.search(query,filter,n,sort);
}
/** @deprecated use {@link #search(Query, Collector)} instead. */
public void search(Query query, HitCollector results) throws IOException {
checkExplanations(query);
super.search(query,results);
search(query, new HitCollectorWrapper(results));
}
public void search(Query query, Collector results) throws IOException {
checkExplanations(query);
super.search(query, results);
}
/** @deprecated use {@link #search(Query, Filter, Collector)} instead. */
public void search(Query query, Filter filter,
HitCollector results) throws IOException {
search(query, filter, new HitCollectorWrapper(results));
}
public void search(Query query, Filter filter, Collector results) throws IOException {
checkExplanations(query);
super.search(query,filter, results);
super.search(query, filter, results);
}
public TopDocs search(Query query, Filter filter,
int n) throws IOException {
@ -416,7 +425,7 @@ public class CheckHits {
*
* @see CheckHits#verifyExplanation
*/
public static class ExplanationAsserter extends MultiReaderHitCollector {
public static class ExplanationAsserter extends Collector {
/**
* @deprecated
@ -429,7 +438,8 @@ public class CheckHits {
String d;
boolean deep;
private int base = -1;
Scorer scorer;
private int base = 0;
/** Constructs an instance which does shallow tests on the Explanation */
public ExplanationAsserter(Query q, String defaultFieldName, Searcher s) {
@ -442,7 +452,11 @@ public class CheckHits {
this.deep=deep;
}
public void collect(int doc, float score) {
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public void collect(int doc) throws IOException {
Explanation exp = null;
doc = doc + base;
try {
@ -454,7 +468,7 @@ public class CheckHits {
TestCase.assertNotNull("Explanation of [["+d+"]] for #"+doc+" is null",
exp);
verifyExplanation(d,doc,score,deep,exp);
verifyExplanation(d,doc,scorer.score(),deep,exp);
}
public void setNextReader(IndexReader reader, int docBase) {
base = docBase;

View File

@ -0,0 +1,580 @@
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.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermPositions;
import org.apache.lucene.util.PriorityQueue;
/**
* Holds all implementations of classes in the o.a.l.search package as a
* back-compatibility test. It does not run any tests per-se, however if
* someone adds a method to an interface or abstract method to an abstract
* class, one of the implementations here will fail to compile and so we know
* back-compat policy was violated.
*/
final class JustCompileSearch {
private static final String UNSUPPORTED_MSG = "unsupported: used for back-compat testing only !";
static final class JustCompileSearchable implements Searchable {
public void close() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Document doc(int i) throws CorruptIndexException, IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Document doc(int n, FieldSelector fieldSelector)
throws CorruptIndexException, IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int docFreq(Term term) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int[] docFreqs(Term[] terms) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Explanation explain(Weight weight, int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int maxDoc() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Query rewrite(Query query) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void search(Weight weight, Filter filter, HitCollector results)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void search(Weight weight, Filter filter, Collector collector)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public TopDocs search(Weight weight, Filter filter, int n)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public TopFieldDocs search(Weight weight, Filter filter, int n, Sort sort)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileSearcher extends Searcher {
public void close() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Document doc(int i) throws CorruptIndexException, IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int docFreq(Term term) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Explanation explain(Weight weight, int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int maxDoc() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Query rewrite(Query query) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void search(Weight weight, Filter filter, HitCollector results)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void search(Weight weight, Filter filter, Collector results)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public TopDocs search(Weight weight, Filter filter, int n)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public TopFieldDocs search(Weight weight, Filter filter, int n, Sort sort)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Document doc(int n, FieldSelector fieldSelector)
throws CorruptIndexException, IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileCollector extends Collector {
public void collect(int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void setNextReader(IndexReader reader, int docBase)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void setScorer(Scorer scorer) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileDocIdSet extends DocIdSet {
public DocIdSetIterator iterator() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileDocIdSetIterator extends DocIdSetIterator {
public int doc() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean next() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean skipTo(int target) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileFieldCache implements FieldCache {
public Object getAuto(IndexReader reader, String field) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public byte[] getBytes(IndexReader reader, String field) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public byte[] getBytes(IndexReader reader, String field, ByteParser parser)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
/** @deprecated */
public Comparable[] getCustom(IndexReader reader, String field,
SortComparator comparator) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float[] getFloats(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float[] getFloats(IndexReader reader, String field,
FloatParser parser) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int[] getInts(IndexReader reader, String field) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int[] getInts(IndexReader reader, String field, IntParser parser)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public short[] getShorts(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public short[] getShorts(IndexReader reader, String field,
ShortParser parser) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public StringIndex getStringIndex(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public String[] getStrings(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileExtendedFieldCache implements ExtendedFieldCache {
public double[] getDoubles(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public double[] getDoubles(IndexReader reader, String field,
DoubleParser parser) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public long[] getLongs(IndexReader reader, String field) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public long[] getLongs(IndexReader reader, String field, LongParser parser)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Object getAuto(IndexReader reader, String field) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public byte[] getBytes(IndexReader reader, String field) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public byte[] getBytes(IndexReader reader, String field, ByteParser parser)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
/** @deprecated */
public Comparable[] getCustom(IndexReader reader, String field,
SortComparator comparator) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float[] getFloats(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float[] getFloats(IndexReader reader, String field,
FloatParser parser) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int[] getInts(IndexReader reader, String field) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int[] getInts(IndexReader reader, String field, IntParser parser)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public short[] getShorts(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public short[] getShorts(IndexReader reader, String field,
ShortParser parser) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public StringIndex getStringIndex(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public String[] getStrings(IndexReader reader, String field)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileExtendedFieldCacheLongParser implements ExtendedFieldCache.LongParser {
public long parseLong(String string) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileExtendedFieldCacheDoubleParser implements ExtendedFieldCache.DoubleParser {
public double parseDouble(String string) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileFieldComparator extends FieldComparator {
public int compare(int slot1, int slot2) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int compareBottom(int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void copy(int slot, int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void setBottom(int slot) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void setNextReader(IndexReader reader, int docBase, int numSlotsFull)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int sortType() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Comparable value(int slot) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileFieldComparatorSource extends FieldComparatorSource {
public FieldComparator newComparator(String fieldname, int numHits,
int sortPos, boolean reversed) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileFilter extends Filter {
// Filter is just an abstract class with no abstract methods. However it is
// still added here in case someone will add abstract methods in the future.
}
static final class JustCompileFilteredDocIdSet extends FilteredDocIdSet {
public JustCompileFilteredDocIdSet(DocIdSet innerSet) {
super(innerSet);
}
protected boolean match(int docid) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileFilteredDocIdSetIterator extends FilteredDocIdSetIterator {
public JustCompileFilteredDocIdSetIterator(DocIdSetIterator innerIter) {
super(innerIter);
}
protected boolean match(int doc) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileFilteredTermEnum extends FilteredTermEnum {
public float difference() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
protected boolean endEnum() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
protected boolean termCompare(Term term) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileMultiTermQuery extends MultiTermQuery {
protected FilteredTermEnum getEnum(IndexReader reader) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompilePhraseScorer extends PhraseScorer {
JustCompilePhraseScorer(Weight weight, TermPositions[] tps, int[] offsets,
Similarity similarity, byte[] norms) {
super(weight, tps, offsets, similarity, norms);
}
protected float phraseFreq() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileQuery extends Query {
public String toString(String field) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileScorer extends Scorer {
protected JustCompileScorer(Similarity similarity) {
super(similarity);
}
public Explanation explain(int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float score() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int doc() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean next() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean skipTo(int target) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileSimilarity extends Similarity {
public float coord(int overlap, int maxOverlap) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float idf(int docFreq, int numDocs) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float lengthNorm(String fieldName, int numTokens) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float queryNorm(float sumOfSquaredWeights) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float sloppyFreq(int distance) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float tf(float freq) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileSpanFilter extends SpanFilter {
public SpanFilterResult bitSpans(IndexReader reader) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileTopDocsCollector extends TopDocsCollector {
protected JustCompileTopDocsCollector(PriorityQueue pq) {
super(pq);
}
public void collect(int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void setNextReader(IndexReader reader, int docBase)
throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void setScorer(Scorer scorer) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileWeight implements Weight {
public Explanation explain(IndexReader reader, int doc) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Query getQuery() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float getValue() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public void normalize(float norm) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Scorer scorer(IndexReader reader) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public float sumOfSquaredWeights() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
}

View File

@ -153,10 +153,15 @@ public class QueryUtils {
final int[] sdoc = new int[] {-1};
final float maxDiff = 1e-5f;
s.search(q,new MultiReaderHitCollector() {
private int base = -1;
public void collect(int doc, float score) {
s.search(q,new Collector() {
private int base = 0;
private Scorer sc;
public void setScorer(Scorer scorer) throws IOException {
this.sc = scorer;
}
public void collect(int doc) throws IOException {
doc = doc + base;
float score = sc.score();
try {
int op = order[(opidx[0]++)%order.length];
//System.out.println(op==skip_op ? "skip("+(sdoc[0]+1)+")":"next()");
@ -205,11 +210,16 @@ public class QueryUtils {
//System.out.println("checkFirstSkipTo: "+q);
final float maxDiff = 1e-5f;
final int lastDoc[] = {-1};
s.search(q,new MultiReaderHitCollector() {
private int base = -1;
public void collect(int doc, float score) {
s.search(q,new Collector() {
private int base = 0;
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public void collect(int doc) throws IOException {
//System.out.println("doc="+doc);
doc = doc + base;
float score = scorer.score();
try {
for (int i=lastDoc[0]+1; i<=doc; i++) {
Weight w = q.weight(s);

View File

@ -17,6 +17,8 @@ package org.apache.lucene.search;
* limitations under the License.
*/
import java.io.IOException;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.*;
@ -66,10 +68,14 @@ public class TestDocBoost extends LuceneTestCase {
new IndexSearcher(store).search
(new TermQuery(new Term("field", "word")),
new MultiReaderHitCollector() {
private int base = -1;
public final void collect(int doc, float score) {
scores[doc + base] = score;
new Collector() {
private int base = 0;
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
scores[doc + base] = scorer.score();
}
public void setNextReader(IndexReader reader, int docBase) {
base = docBase;

View File

@ -168,10 +168,14 @@ public class TestMultiTermConstantScore extends BaseTestRangeFilter {
// must use a non score normalizing method for this.
Query q = csrq("data", "1", "6", T, T);
q.setBoost(100);
search.search(q, null, new MultiReaderHitCollector() {
private int base = -1;
public void collect(int doc, float score) {
assertEquals("score for doc " + (doc + base) + " was not correct", 1.0f, score);
search.search(q, null, new Collector() {
private int base = 0;
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public void collect(int doc) throws IOException {
assertEquals("score for doc " + (doc + base) + " was not correct", 1.0f, scorer.score());
}
public void setNextReader(IndexReader reader, int docBase) {
base = docBase;

View File

@ -0,0 +1,85 @@
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.util.LuceneTestCase;
public class TestPositiveScoresOnlyCollector extends LuceneTestCase {
private static final class SimpleScorer extends Scorer {
private int idx = -1;
public SimpleScorer() {
super(null);
}
public Explanation explain(int doc) throws IOException { return null; }
public float score() throws IOException {
return idx == scores.length ? Float.NaN : scores[idx];
}
public int doc() { return idx; }
public boolean next() throws IOException {
return ++idx == scores.length;
}
public boolean skipTo(int target) throws IOException {
idx = target;
return idx >= scores.length;
}
}
// The scores must have positive as well as negative values
private static final float[] scores = new float[] { 0.7767749f, -1.7839992f,
8.9925785f, 7.9608946f, -0.07948637f, 2.6356435f, 7.4950366f, 7.1490803f,
-8.108544f, 4.961808f, 2.2423935f, -7.285586f, 4.6699767f };
public void testNegativeScores() throws Exception {
// The Top*Collectors previously filtered out documents with <= scores. This
// behavior has changed. This test checks that if PositiveOnlyScoresFilter
// wraps one of these collectors, documents with <= 0 scores are indeed
// filtered.
int numPositiveScores = 0;
for (int i = 0; i < scores.length; i++) {
if (scores[i] > 0) {
++numPositiveScores;
}
}
Scorer s = new SimpleScorer();
TopDocsCollector tdc = new TopScoreDocCollector(scores.length);
Collector c = new PositiveScoresOnlyCollector(tdc);
c.setScorer(s);
while (!s.next()) {
c.collect(0);
}
TopDocs td = tdc.topDocs();
ScoreDoc[] sd = td.scoreDocs;
assertEquals(numPositiveScores, td.totalHits);
for (int i = 0; i < sd.length; i++) {
assertTrue("only positive scores should return: " + sd[i].score, sd[i].score > 0);
}
}
}

View File

@ -0,0 +1,111 @@
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;
import org.apache.lucene.util.LuceneTestCase;
public class TestScoreCachingWrappingScorer extends LuceneTestCase {
private static final class SimpleScorer extends Scorer {
private int idx = 0;
private int doc = -1;
public SimpleScorer() {
super(null);
}
public Explanation explain(int doc) throws IOException { return null; }
public float score() throws IOException {
// advance idx on purpose, so that consecutive calls to score will get
// different results. This is to emulate computation of a score. If
// ScoreCachingWrappingScorer is used, this should not be called more than
// once per document.
return idx == scores.length ? Float.NaN : scores[idx++];
}
public int doc() { return doc; }
public boolean next() throws IOException {
return ++doc == scores.length;
}
public boolean skipTo(int target) throws IOException {
doc = target;
return doc >= scores.length;
}
}
private static final class ScoreCachingCollector extends Collector {
private int idx = 0;
private Scorer scorer;
float[] mscores;
public ScoreCachingCollector(int numToCollect) {
mscores = new float[numToCollect];
}
public void collect(int doc) throws IOException {
// just a sanity check to avoid IOOB.
if (idx == mscores.length) {
return;
}
// just call score() a couple of times and record the score.
mscores[idx] = scorer.score();
mscores[idx] = scorer.score();
mscores[idx] = scorer.score();
++idx;
}
public void setNextReader(IndexReader reader, int docBase)
throws IOException {
}
public void setScorer(Scorer scorer) throws IOException {
this.scorer = new ScoreCachingWrappingScorer(scorer);
}
}
private static final float[] scores = new float[] { 0.7767749f, 1.7839992f,
8.9925785f, 7.9608946f, 0.07948637f, 2.6356435f, 7.4950366f, 7.1490803f,
8.108544f, 4.961808f, 2.2423935f, 7.285586f, 4.6699767f };
public void testGetScores() throws Exception {
Scorer s = new SimpleScorer();
ScoreCachingCollector scc = new ScoreCachingCollector(scores.length);
scc.setScorer(s);
// We need to iterate on the scorer so that its doc() advances.
while (!s.next()) {
scc.collect(s.doc());
}
for (int i = 0; i < scores.length; i++) {
assertEquals(scores[i], scc.mscores[i], 0f);
}
}
}

View File

@ -96,14 +96,16 @@ public class TestScorerPerf extends LuceneTestCase {
return sets;
}
public static class CountingHitCollector extends MultiReaderHitCollector {
public static class CountingHitCollector extends Collector {
int count=0;
int sum=0;
protected int docBase = -1;
protected int docBase = 0;
public void collect(int doc, float score) {
public void setScorer(Scorer scorer) throws IOException {}
public void collect(int doc) {
count++;
sum += docBase+doc; // use it to avoid any possibility of being optimized away
sum += docBase + doc; // use it to avoid any possibility of being optimized away
}
public int getCount() { return count; }
@ -123,11 +125,12 @@ public class TestScorerPerf extends LuceneTestCase {
}
public void collect(int doc, float score) {
pos = answer.nextSetBit(pos+1);
if (pos != doc + docBase) {
throw new RuntimeException("Expected doc " + pos + " but got " + doc + docBase);
}
super.collect(doc,score);
super.collect(doc);
}
}

View File

@ -17,6 +17,8 @@ package org.apache.lucene.search;
* limitations under the License.
*/
import java.io.IOException;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.*;
@ -62,10 +64,14 @@ public class TestSetNorm extends LuceneTestCase {
new IndexSearcher(store).search
(new TermQuery(new Term("field", "word")),
new MultiReaderHitCollector() {
private int base = -1;
public final void collect(int doc, float score) {
scores[doc + base] = score;
new Collector() {
private int base = 0;
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
scores[doc + base] = scorer.score();
}
public void setNextReader(IndexReader reader, int docBase) {
base = docBase;

View File

@ -19,6 +19,7 @@ package org.apache.lucene.search;
import org.apache.lucene.util.LuceneTestCase;
import java.io.IOException;
import java.util.Collection;
import org.apache.lucene.index.IndexReader;
@ -75,9 +76,13 @@ public class TestSimilarity extends LuceneTestCase {
searcher.search
(new TermQuery(b),
new MultiReaderHitCollector() {
public final void collect(int doc, float score) {
assertTrue(score == 1.0f);
new Collector() {
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
assertTrue(scorer.score() == 1.0f);
}
public void setNextReader(IndexReader reader, int docBase) {}
});
@ -88,11 +93,15 @@ public class TestSimilarity extends LuceneTestCase {
//System.out.println(bq.toString("field"));
searcher.search
(bq,
new MultiReaderHitCollector() {
private int base = -1;
public final void collect(int doc, float score) {
new Collector() {
private int base = 0;
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
//System.out.println("Doc=" + doc + " score=" + score);
assertTrue(score == (float)doc+base+1);
assertTrue(scorer.score() == (float)doc+base+1);
}
public void setNextReader(IndexReader reader, int docBase) {
base = docBase;
@ -105,10 +114,14 @@ public class TestSimilarity extends LuceneTestCase {
//System.out.println(pq.toString("field"));
searcher.search
(pq,
new MultiReaderHitCollector() {
public final void collect(int doc, float score) {
new Collector() {
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
//System.out.println("Doc=" + doc + " score=" + score);
assertTrue(score == 1.0f);
assertTrue(scorer.score() == 1.0f);
}
public void setNextReader(IndexReader reader, int docBase) {}
});
@ -117,10 +130,14 @@ public class TestSimilarity extends LuceneTestCase {
//System.out.println(pq.toString("field"));
searcher.search
(pq,
new MultiReaderHitCollector() {
public final void collect(int doc, float score) {
new Collector() {
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public final void collect(int doc) throws IOException {
//System.out.println("Doc=" + doc + " score=" + score);
assertTrue(score == 2.0f);
assertTrue(scorer.score() == 2.0f);
}
public void setNextReader(IndexReader reader, int docBase) {}
});

View File

@ -413,7 +413,7 @@ implements Serializable {
slotValues = new int[numHits];
}
public void copy(int slot, int doc, float score) {
public void copy(int slot, int doc) {
slotValues[slot] = docValues[doc];
}
@ -421,7 +421,7 @@ implements Serializable {
return slotValues[slot1] - slotValues[slot2];
}
public int compareBottom(int doc, float score) {
public int compareBottom(int doc) {
return bottomValue - docValues[doc];
}
@ -447,7 +447,7 @@ implements Serializable {
}
static class MyFieldComparatorSource extends FieldComparatorSource {
public FieldComparator newComparator(String fieldname, IndexReader[] subReaders, int numHits, int sortPos, boolean reversed) {
public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed) {
return new MyFieldComparator(numHits);
}
}
@ -804,6 +804,93 @@ implements Serializable {
assertEquals(docs1.scoreDocs[0].score, docs2.scoreDocs[0].score, 1e-6);
}
public void testSortWithoutFillFields() throws Exception {
// There was previously a bug in TopFieldCollector when fillFields was set
// to false - the same doc and score was set in ScoreDoc[] array. This test
// asserts that if fillFields is false, the documents are set properly. It
// does not use Searcher's default search methods (with Sort) since all set
// fillFields to true.
Sort[] sort = new Sort[] { new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, false, false, false);
full.search(new MatchAllDocsQuery(), tdc);
ScoreDoc[] sd = tdc.topDocs().scoreDocs;
for (int j = 1; j < sd.length; j++) {
assertTrue(sd[j].doc != sd[j - 1].doc);
}
}
}
public void testSortWithoutScoreTracking() throws Exception {
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, false, false);
full.search(new MatchAllDocsQuery(), tdc);
TopDocs td = tdc.topDocs();
ScoreDoc[] sd = td.scoreDocs;
for (int j = 0; j < sd.length; j++) {
assertTrue(Float.isNaN(sd[j].score));
}
assertTrue(Float.isNaN(td.getMaxScore()));
}
}
public void testSortWithScoreNoMaxScoreTracking() throws Exception {
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true, false);
full.search(new MatchAllDocsQuery(), tdc);
TopDocs td = tdc.topDocs();
ScoreDoc[] sd = td.scoreDocs;
for (int j = 0; j < sd.length; j++) {
assertTrue(!Float.isNaN(sd[j].score));
}
assertTrue(Float.isNaN(td.getMaxScore()));
}
}
public void testSortWithScoreAndMaxScoreTracking() throws Exception {
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true, true);
full.search(new MatchAllDocsQuery(), tdc);
TopDocs td = tdc.topDocs();
ScoreDoc[] sd = td.scoreDocs;
for (int j = 0; j < sd.length; j++) {
assertTrue(!Float.isNaN(sd[j].score));
}
assertTrue(!Float.isNaN(td.getMaxScore()));
}
}
public void testSortWithScoreAndMaxScoreTrackingNoResults() throws Exception {
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true, true);
TopDocs td = tdc.topDocs();
assertEquals(0, td.totalHits);
assertTrue(Float.isNaN(td.getMaxScore()));
}
}
// runs a variety of sorts useful for multisearchers
private void runMultiSorts (Searcher multi) throws Exception {
sort.setSort (SortField.FIELD_DOC);

View File

@ -65,8 +65,7 @@ public class TestTermScorer extends LuceneTestCase
}
public void test() throws IOException
{
public void test() throws IOException {
Term allTerm = new Term(FIELD, "all");
TermQuery termQuery = new TermQuery(allTerm);
@ -76,21 +75,25 @@ public class TestTermScorer extends LuceneTestCase
TermScorer ts = new TermScorer(weight,
indexReader.termDocs(allTerm), indexSearcher.getSimilarity(),
indexReader.norms(FIELD));
assertTrue("ts is null and it shouldn't be", ts != null);
//we have 2 documents with the term all in them, one document for all the other values
final List docs = new ArrayList();
//must call next first
ts.score(new MultiReaderHitCollector()
{
private int base = -1;
public void collect(int doc, float score)
{
docs.add(new TestHit(doc + base, score));
assertTrue("score " + score + " is not greater than 0", score > 0);
assertTrue("Doc: " + doc + " does not equal: " + 0 +
" or doc does not equaal: " + 5, doc == 0 || doc == 5);
ts.score(new Collector() {
private int base = 0;
private Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public void collect(int doc) throws IOException {
float score = scorer.score();
doc = doc + base;
docs.add(new TestHit(doc, score));
assertTrue("score " + score + " is not greater than 0", score > 0);
assertTrue("Doc: " + doc + " does not equal 0 or doc does not equal 5",
doc == 0 || doc == 5);
}
public void setNextReader(IndexReader reader, int docBase) {
base = docBase;
@ -121,8 +124,7 @@ public class TestTermScorer extends LuceneTestCase
assertTrue(doc0.score + " does not equal: " + 1.6931472f, doc0.score == 1.6931472f);
}
public void testNext() throws Exception
{
public void testNext() throws Exception {
Term allTerm = new Term(FIELD, "all");
TermQuery termQuery = new TermQuery(allTerm);
@ -132,7 +134,6 @@ public class TestTermScorer extends LuceneTestCase
TermScorer ts = new TermScorer(weight,
indexReader.termDocs(allTerm), indexSearcher.getSimilarity(),
indexReader.norms(FIELD));
assertTrue("ts is null and it shouldn't be", ts != null);
assertTrue("next did not return a doc", ts.next() == true);
assertTrue("score is not correct", ts.score() == 1.6931472f);
assertTrue("next did not return a doc", ts.next() == true);
@ -140,8 +141,7 @@ public class TestTermScorer extends LuceneTestCase
assertTrue("next returned a doc and it should not have", ts.next() == false);
}
public void testSkipTo() throws Exception
{
public void testSkipTo() throws Exception {
Term allTerm = new Term(FIELD, "all");
TermQuery termQuery = new TermQuery(allTerm);
@ -151,7 +151,6 @@ public class TestTermScorer extends LuceneTestCase
TermScorer ts = new TermScorer(weight,
indexReader.termDocs(allTerm), indexSearcher.getSimilarity(),
indexReader.norms(FIELD));
assertTrue("ts is null and it shouldn't be", ts != null);
assertTrue("Didn't skip", ts.skipTo(3) == true);
//The next doc should be doc 5
assertTrue("doc should be number 5", ts.doc() == 5);
@ -167,7 +166,6 @@ public class TestTermScorer extends LuceneTestCase
TermScorer ts = new TermScorer(weight,
indexReader.termDocs(allTerm), indexSearcher.getSimilarity(),
indexReader.norms(FIELD));
assertTrue("ts is null and it shouldn't be", ts != null);
Explanation explanation = ts.explain(0);
assertTrue("explanation is null and it shouldn't be", explanation != null);
//System.out.println("Explanation: " + explanation.toString());
@ -185,7 +183,6 @@ public class TestTermScorer extends LuceneTestCase
ts = new TermScorer(weight, indexReader.termDocs(dogsTerm), indexSearcher.getSimilarity(),
indexReader.norms(FIELD));
assertTrue("ts is null and it shouldn't be", ts != null);
explanation = ts.explain(1);
assertTrue("explanation is null and it shouldn't be", explanation != null);
//System.out.println("Explanation: " + explanation.toString());
@ -202,23 +199,17 @@ public class TestTermScorer extends LuceneTestCase
}
private class TestHit
{
private class TestHit {
public int doc;
public float score;
public TestHit(int doc, float score)
{
public TestHit(int doc, float score) {
this.doc = doc;
this.score = score;
}
public String toString()
{
return "TestHit{" +
"doc=" + doc +
", score=" + score +
"}";
public String toString() {
return "TestHit{" + "doc=" + doc + ", score=" + score + "}";
}
}

View File

@ -20,7 +20,6 @@ package org.apache.lucene.search;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.queryParser.QueryParser;
@ -287,12 +286,11 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
}
// counting hit collector that can slow down at collect().
private class MyHitCollector extends MultiReaderHitCollector
private class MyHitCollector extends HitCollector
{
private final BitSet bits = new BitSet();
private int slowdown = 0;
private int lastDocCollected = -1;
private int docBase = -1;
/**
* amount of time to wait on each collect to simulate a long iteration
@ -301,8 +299,7 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
slowdown = milliseconds;
}
public void collect( final int doc, final float score ) {
int docId = doc + docBase;
public void collect( final int docId, final float score ) {
if( slowdown > 0 ) {
try {
Thread.sleep(slowdown);
@ -311,7 +308,7 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
throw new RuntimeException(ie);
}
}
assert docId >= 0: " base=" + docBase + " doc=" + doc;
assert docId >= 0: " doc=" + docId;
bits.set( docId );
lastDocCollected = docId;
}
@ -323,11 +320,6 @@ public class TestTimeLimitedCollector extends LuceneTestCase {
public int getLastDocCollected() {
return lastDocCollected;
}
public void setNextReader(IndexReader reader, int base) {
docBase = base;
}
}
}

View File

@ -0,0 +1,337 @@
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 java.util.BitSet;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.TimeLimitingCollector.TimeExceededException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.LuceneTestCase;
/**
* Tests the {@link TimeLimitingCollector}. This test checks (1) search
* correctness (regardless of timeout), (2) expected timeout behavior,
* and (3) a sanity test with multiple searching threads.
*/
public class TestTimeLimitingCollector extends LuceneTestCase {
private static final int SLOW_DOWN = 47;
private static final long TIME_ALLOWED = 17 * SLOW_DOWN; // so searches can find about 17 docs.
// max time allowed is relaxed for multithreading tests.
// the multithread case fails when setting this to 1 (no slack) and launching many threads (>2000).
// but this is not a real failure, just noise.
private static final double MULTI_THREAD_SLACK = 7;
private static final int N_DOCS = 3000;
private static final int N_THREADS = 50;
private Searcher searcher;
private final String FIELD_NAME = "body";
private Query query;
public TestTimeLimitingCollector(String name) {
super(name);
}
/**
* initializes searcher with a document set
*/
protected void setUp() throws Exception {
final String docText[] = {
"docThatNeverMatchesSoWeCanRequireLastDocCollectedToBeGreaterThanZero",
"one blah three",
"one foo three multiOne",
"one foobar three multiThree",
"blueberry pancakes",
"blueberry pie",
"blueberry strudel",
"blueberry pizza",
};
Directory directory = new RAMDirectory();
IndexWriter iw = new IndexWriter(directory, new WhitespaceAnalyzer(), true, MaxFieldLength.UNLIMITED);
for (int i=0; i<N_DOCS; i++) {
add(docText[i%docText.length], iw);
}
iw.close();
searcher = new IndexSearcher(directory);
String qtxt = "one";
for (int i = 0; i < docText.length; i++) {
qtxt += ' ' + docText[i]; // large query so that search will be longer
}
QueryParser queryParser = new QueryParser(FIELD_NAME, new WhitespaceAnalyzer());
query = queryParser.parse(qtxt);
// warm the searcher
searcher.search(query, null, 1000);
}
public void tearDown() throws Exception {
searcher.close();
}
private void add(String value, IndexWriter iw) throws IOException {
Document d = new Document();
d.add(new Field(FIELD_NAME, value, Field.Store.NO, Field.Index.ANALYZED));
iw.addDocument(d);
}
private void search(Collector collector) throws Exception {
searcher.search(query, collector);
}
/**
* test search correctness with no timeout
*/
public void testSearch() {
doTestSearch();
}
private void doTestSearch() {
int totalResults = 0;
int totalTLCResults = 0;
try {
MyHitCollector myHc = new MyHitCollector();
search(myHc);
totalResults = myHc.hitCount();
myHc = new MyHitCollector();
long oneHour = 3600000;
Collector tlCollector = createTimedCollector(myHc, oneHour, false);
search(tlCollector);
totalTLCResults = myHc.hitCount();
} catch (Exception e) {
e.printStackTrace();
assertTrue("Unexpected exception: "+e, false); //==fail
}
assertEquals( "Wrong number of results!", totalResults, totalTLCResults );
}
private Collector createTimedCollector(MyHitCollector hc, long timeAllowed, boolean greedy) {
TimeLimitingCollector res = new TimeLimitingCollector(hc, timeAllowed);
res.setGreedy(greedy); // set to true to make sure at least one doc is collected.
return res;
}
/**
* Test that timeout is obtained, and soon enough!
*/
public void testTimeoutGreedy() {
doTestTimeout(false, true);
}
/**
* Test that timeout is obtained, and soon enough!
*/
public void testTimeoutNotGreedy() {
doTestTimeout(false, false);
}
private void doTestTimeout(boolean multiThreaded, boolean greedy) {
// setup
MyHitCollector myHc = new MyHitCollector();
myHc.setSlowDown(SLOW_DOWN);
Collector tlCollector = createTimedCollector(myHc, TIME_ALLOWED, greedy);
// search
TimeExceededException timoutException = null;
try {
search(tlCollector);
} catch (TimeExceededException x) {
timoutException = x;
} catch (Exception e) {
assertTrue("Unexpected exception: "+e, false); //==fail
}
// must get exception
assertNotNull( "Timeout expected!", timoutException );
// greediness affect last doc collected
int exceptionDoc = timoutException.getLastDocCollected();
int lastCollected = myHc.getLastDocCollected();
assertTrue( "doc collected at timeout must be > 0!", exceptionDoc > 0 );
if (greedy) {
assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" != lastCollected="+lastCollected, exceptionDoc==lastCollected);
assertTrue("greedy, but no hits found!", myHc.hitCount() > 0 );
} else {
assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" not > lastCollected="+lastCollected, exceptionDoc>lastCollected);
}
// verify that elapsed time at exception is within valid limits
assertEquals( timoutException.getTimeAllowed(), TIME_ALLOWED);
// a) Not too early
assertTrue ( "elapsed="+timoutException.getTimeElapsed()+" <= (allowed-resolution)="+(TIME_ALLOWED-TimeLimitingCollector.getResolution()),
timoutException.getTimeElapsed() > TIME_ALLOWED-TimeLimitingCollector.getResolution());
// b) Not too late.
// This part is problematic in a busy test system, so we just print a warning.
// We already verified that a timeout occurred, we just can't be picky about how long it took.
if (timoutException.getTimeElapsed() > maxTime(multiThreaded)) {
System.out.println("Informative: timeout exceeded (no action required: most probably just " +
" because the test machine is slower than usual): " +
"lastDoc="+exceptionDoc+
" ,&& allowed="+timoutException.getTimeAllowed() +
" ,&& elapsed="+timoutException.getTimeElapsed() +
" >= " + maxTimeStr(multiThreaded));
}
}
private long maxTime(boolean multiThreaded) {
long res = 2 * TimeLimitingCollector.getResolution() + TIME_ALLOWED + SLOW_DOWN; // some slack for less noise in this test
if (multiThreaded) {
res *= MULTI_THREAD_SLACK; // larger slack
}
return res;
}
private String maxTimeStr(boolean multiThreaded) {
String s =
"( " +
"2*resolution + TIME_ALLOWED + SLOW_DOWN = " +
"2*" + TimeLimitingCollector.getResolution() + " + " + TIME_ALLOWED + " + " + SLOW_DOWN +
")";
if (multiThreaded) {
s = MULTI_THREAD_SLACK + " * "+s;
}
return maxTime(multiThreaded) + " = " + s;
}
/**
* Test timeout behavior when resolution is modified.
*/
public void testModifyResolution() {
try {
// increase and test
long resolution = 20 * TimeLimitingCollector.DEFAULT_RESOLUTION; //400
TimeLimitingCollector.setResolution(resolution);
assertEquals(resolution, TimeLimitingCollector.getResolution());
doTestTimeout(false,true);
// decrease much and test
resolution = 5;
TimeLimitingCollector.setResolution(resolution);
assertEquals(resolution, TimeLimitingCollector.getResolution());
doTestTimeout(false,true);
// return to default and test
resolution = TimeLimitingCollector.DEFAULT_RESOLUTION;
TimeLimitingCollector.setResolution(resolution);
assertEquals(resolution, TimeLimitingCollector.getResolution());
doTestTimeout(false,true);
} finally {
TimeLimitingCollector.setResolution(TimeLimitingCollector.DEFAULT_RESOLUTION);
}
}
/**
* Test correctness with multiple searching threads.
*/
public void testSearchMultiThreaded() throws Exception {
doTestMultiThreads(false);
}
/**
* Test correctness with multiple searching threads.
*/
public void testTimeoutMultiThreaded() throws Exception {
doTestMultiThreads(true);
}
private void doTestMultiThreads(final boolean withTimeout) throws Exception {
Thread [] threadArray = new Thread[N_THREADS];
final BitSet success = new BitSet(N_THREADS);
for( int i = 0; i < threadArray.length; ++i ) {
final int num = i;
threadArray[num] = new Thread() {
public void run() {
if (withTimeout) {
doTestTimeout(true,true);
} else {
doTestSearch();
}
synchronized(success) {
success.set(num);
}
}
};
}
for( int i = 0; i < threadArray.length; ++i ) {
threadArray[i].start();
}
for( int i = 0; i < threadArray.length; ++i ) {
threadArray[i].join();
}
assertEquals("some threads failed!", N_THREADS,success.cardinality());
}
// counting collector that can slow down at collect().
private class MyHitCollector extends Collector {
private final BitSet bits = new BitSet();
private int slowdown = 0;
private int lastDocCollected = -1;
private int docBase = 0;
/**
* amount of time to wait on each collect to simulate a long iteration
*/
public void setSlowDown( int milliseconds ) {
slowdown = milliseconds;
}
public int hitCount() {
return bits.cardinality();
}
public int getLastDocCollected() {
return lastDocCollected;
}
public void setScorer(Scorer scorer) throws IOException {
// scorer is not needed
}
public void collect(final int doc) throws IOException {
int docId = doc + docBase;
if( slowdown > 0 ) {
try {
Thread.sleep(slowdown);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
assert docId >= 0: " base=" + docBase + " doc=" + doc;
bits.set( docId );
lastDocCollected = docId;
}
public void setNextReader(IndexReader reader, int base) {
docBase = base;
}
}
}

View File

@ -0,0 +1,198 @@
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.analysis.KeywordAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.LuceneTestCase;
public class TestTopDocsCollector extends LuceneTestCase {
private static final class MyTopsDocCollector extends TopDocsCollector {
private int idx = 0;
private int base = 0;
public MyTopsDocCollector(int size) {
super(new HitQueue(size));
}
protected TopDocs newTopDocs(ScoreDoc[] results, int start) {
if (results == null) {
return EMPTY_TOPDOCS;
}
float maxScore = Float.NaN;
if (start == 0) {
maxScore = results[0].score;
} else {
for (int i = pq.size(); i > 1; i--) { pq.pop(); }
maxScore = ((ScoreDoc) pq.pop()).score;
}
return new TopDocs(totalHits, results, maxScore);
}
public void collect(int doc) throws IOException {
pq.insert(new ScoreDoc(doc + base, scores[idx++]));
}
public void setNextReader(IndexReader reader, int docBase)
throws IOException {
base = docBase;
}
public void setScorer(Scorer scorer) throws IOException {
// Don't do anything. Assign scores in random
}
}
// Scores array to be used by MyTopDocsCollector. If it is changed, MAX_SCORE
// must also change.
private static final float[] scores = new float[] {
0.7767749f, 1.7839992f, 8.9925785f, 7.9608946f, 0.07948637f, 2.6356435f,
7.4950366f, 7.1490803f, 8.108544f, 4.961808f, 2.2423935f, 7.285586f, 4.6699767f,
2.9655676f, 6.953706f, 5.383931f, 6.9916306f, 8.365894f, 7.888485f, 8.723962f,
3.1796896f, 0.39971232f, 1.3077754f, 6.8489285f, 9.17561f, 5.060466f, 7.9793315f,
8.601509f, 4.1858315f, 0.28146625f
};
private static final float MAX_SCORE = 9.17561f;
private Directory dir = new RAMDirectory();
private TopDocsCollector doSearch(int numResults) throws IOException {
Query q = new MatchAllDocsQuery();
IndexSearcher searcher = new IndexSearcher(dir);
TopDocsCollector tdc = new MyTopsDocCollector(numResults);
searcher.search(q, tdc);
searcher.close();
return tdc;
}
protected void setUp() throws Exception {
super.setUp();
// populate an index with 30 documents, this should be enough for the test.
// The documents have no content - the test uses MatchAllDocsQuery().
IndexWriter writer = new IndexWriter(dir, new KeywordAnalyzer(), MaxFieldLength.UNLIMITED);
for (int i = 0; i < 30; i++) {
writer.addDocument(new Document());
}
writer.close();
}
protected void tearDown() throws Exception {
dir.close();
dir = null;
super.tearDown();
}
public void testInvalidArguments() throws Exception {
int numResults = 5;
TopDocsCollector tdc = doSearch(numResults);
// start < 0
assertEquals(0, tdc.topDocs(-1).scoreDocs.length);
// start > pq.size()
assertEquals(0, tdc.topDocs(numResults + 1).scoreDocs.length);
// start == pq.size()
assertEquals(0, tdc.topDocs(numResults).scoreDocs.length);
// howMany < 0
assertEquals(0, tdc.topDocs(0, -1).scoreDocs.length);
// howMany == 0
assertEquals(0, tdc.topDocs(0, 0).scoreDocs.length);
}
public void testZeroResults() throws Exception {
TopDocsCollector tdc = new MyTopsDocCollector(5);
assertEquals(0, tdc.topDocs(0, 1).scoreDocs.length);
}
public void testFirstResultsPage() throws Exception {
TopDocsCollector tdc = doSearch(15);
assertEquals(10, tdc.topDocs(0, 10).scoreDocs.length);
}
public void testSecondResultsPages() throws Exception {
TopDocsCollector tdc = doSearch(15);
// ask for more results than are available
assertEquals(5, tdc.topDocs(10, 10).scoreDocs.length);
// ask for 5 results (exactly what there should be
tdc = doSearch(15);
assertEquals(5, tdc.topDocs(10, 5).scoreDocs.length);
// ask for less results than there are
tdc = doSearch(15);
assertEquals(4, tdc.topDocs(10, 4).scoreDocs.length);
}
public void testGetAllResults() throws Exception {
TopDocsCollector tdc = doSearch(15);
assertEquals(15, tdc.topDocs().scoreDocs.length);
}
public void testGetResultsFromStart() throws Exception {
TopDocsCollector tdc = doSearch(15);
// should bring all results
assertEquals(15, tdc.topDocs(0).scoreDocs.length);
tdc = doSearch(15);
// get the last 5 only.
assertEquals(5, tdc.topDocs(10).scoreDocs.length);
}
public void testMaxScore() throws Exception {
// ask for all results
TopDocsCollector tdc = doSearch(15);
TopDocs td = tdc.topDocs();
assertEquals(MAX_SCORE, td.getMaxScore(), 0f);
// ask for 5 last results
tdc = doSearch(15);
td = tdc.topDocs(10);
assertEquals(MAX_SCORE, td.getMaxScore(), 0f);
}
// This does not test the PQ's correctness, but whether topDocs()
// implementations return the results in decreasing score order.
public void testResultsOrder() throws Exception {
TopDocsCollector tdc = doSearch(15);
ScoreDoc[] sd = tdc.topDocs().scoreDocs;
assertEquals(MAX_SCORE, sd[0].score, 0f);
for (int i = 1; i < sd.length; i++) {
assertTrue(sd[i - 1].score >= sd[i].score);
}
}
}

View File

@ -0,0 +1,89 @@
package org.apache.lucene.search.function;
/**
* 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;
import org.apache.lucene.search.FieldCache;
/**
* Holds all implementations of classes in the o.a.l.s.function package as a
* back-compatibility test. It does not run any tests per-se, however if
* someone adds a method to an interface or abstract method to an abstract
* class, one of the implementations here will fail to compile and so we know
* back-compat policy was violated.
*/
final class JustCompileSearchFunction {
private static final String UNSUPPORTED_MSG = "unsupported: used for back-compat testing only !";
static final class JustCompileDocValues extends DocValues {
public float floatVal(int doc) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public String toString(int doc) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileFieldCacheSource extends FieldCacheSource {
public JustCompileFieldCacheSource(String field) {
super(field);
}
public boolean cachedFieldSourceEquals(FieldCacheSource other) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int cachedFieldSourceHashCode() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public DocValues getCachedFieldValues(FieldCache cache, String field,
IndexReader reader) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileValueSource extends ValueSource {
public String description() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean equals(Object o) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public DocValues getValues(IndexReader reader) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int hashCode() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
}

View File

@ -0,0 +1,112 @@
package org.apache.lucene.search.spans;
/**
* 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 java.util.Collection;
import org.apache.lucene.index.IndexReader;
/**
* Holds all implementations of classes in the o.a.l.s.spans package as a
* back-compatibility test. It does not run any tests per-se, however if
* someone adds a method to an interface or abstract method to an abstract
* class, one of the implementations here will fail to compile and so we know
* back-compat policy was violated.
*/
final class JustCompileSearchSpans {
private static final String UNSUPPORTED_MSG = "unsupported: used for back-compat testing only !";
static final class JustCompileSpans implements Spans {
public int doc() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int end() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean next() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean skipTo(int target) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int start() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompileSpanQuery extends SpanQuery {
public String getField() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Spans getSpans(IndexReader reader) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public Collection getTerms() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public String toString(String field) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
static final class JustCompilePayloadSpans implements PayloadSpans {
public Collection getPayload() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean isPayloadAvailable() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int doc() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int end() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean next() throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public boolean skipTo(int target) throws IOException {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
public int start() {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
}
}