Index: lucene/misc/src/test/org/apache/lucene/search/TestSwitchingSimilarityCollector.java =================================================================== --- lucene/misc/src/test/org/apache/lucene/search/TestSwitchingSimilarityCollector.java (revision 0) +++ lucene/misc/src/test/org/apache/lucene/search/TestSwitchingSimilarityCollector.java (working copy) @@ -0,0 +1,114 @@ +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.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.similarities.BM25Similarity; +import org.apache.lucene.search.similarities.DefaultSimilarity; +import org.apache.lucene.search.similarities.LMJelinekMercerSimilarity; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.LuceneTestCase; + +public class TestSwitchingSimilarityCollector extends LuceneTestCase { + + public void testBasic() throws Exception { + Directory dir = newDirectory(); + + IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, + new MockAnalyzer(random())); + iwc.setSimilarity(new SwitchingSimilarity(new DefaultSimilarity(), + new BM25Similarity())); + RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc); + + Document doc = new Document(); + doc.add(newTextField("field", "a x", Field.Store.NO)); + w.addDocument(doc); + + doc.add(newTextField("field", "a y", Field.Store.NO)); + w.addDocument(doc); + + doc.add(newTextField("field", "b x", Field.Store.NO)); + w.addDocument(doc); + + IndexReader r = w.getReader(); + w.close(); + + // TermQuery + runQuery(r, new TermQuery(new Term("field", "a"))); + + // BooleanQuery + BooleanQuery bq = new BooleanQuery(); + bq.add(new TermQuery(new Term("field", "a")), BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("field", "y")), BooleanClause.Occur.SHOULD); + runQuery(r, bq); + + r.close(); + dir.close(); + } + + private void runQuery(IndexReader r, Query q) throws Exception { + if (VERBOSE) { + System.out.println("\nTEST: run query=" + q); + } + + TopScoreDocCollector c1 = TopScoreDocCollector.create(10, true); + TopScoreDocCollector c2 = TopScoreDocCollector.create(10, true); + TopScoreDocCollector c3 = TopScoreDocCollector.create(10, true); + SwitchingSimilarity sim = new SwitchingSimilarity(new DefaultSimilarity(), + new BM25Similarity(), + new LMJelinekMercerSimilarity(0.3f)); + + // NOTE: we must make a new searcher for every query, + // because SwitchingSimilarity has per-hit state. This + // is fine (it's cheap: basically just wraps the + // reader): + IndexSearcher s = newSearcher(r); + s.setSimilarity(sim); + s.search(q, + new SwitchingSimilarityCollector(sim, c1, c2, c3)); + + TopDocs hits1 = c1.topDocs(); + TopDocs hits2 = c2.topDocs(); + TopDocs hits3 = c3.topDocs(); + + assertEquals(hits1.totalHits, hits2.totalHits); + assertEquals(hits1.totalHits, hits3.totalHits); + assertEquals(hits1.scoreDocs.length, hits2.scoreDocs.length); + assertEquals(hits1.scoreDocs.length, hits3.scoreDocs.length); + boolean sawDifference = false; + for(int i=0;iThere can easily be cases where this does not work, + * e.g. if the {@link ScoreCachingWrapperScorer} is involved + * in the scoring chain. + * + * @lucene.experimental */ + +public final class SwitchingSimilarityCollector extends Collector { + private final Collector[] subs; + private final SwitchingSimilarity switchingSim; + private Scorer scorer; + + public SwitchingSimilarityCollector(SwitchingSimilarity switchingSim, Collector... subs) { + this.subs = subs; + this.switchingSim = switchingSim; + } + + @Override + public void setScorer(Scorer scorer) throws IOException { + this.scorer = scorer; + for(Collector sub : subs) { + sub.setScorer(scorer); + } + } + + @Override + public void collect(int docID) throws IOException { + System.out.println("collect doc=" + docID + " scorer=" + scorer); + for(Collector sub : subs) { + switchingSim.switchScorer(); + System.out.println(" score=" + scorer.score()); + sub.collect(docID); + } + } + + @Override + public void setNextReader(AtomicReaderContext context) throws IOException { + for(Collector sub : subs) { + sub.setNextReader(context); + } + } + + @Override + public boolean acceptsDocsOutOfOrder() { + // We must return false here, because we need the + // .score() to not be cached for each hit: + return false; + } +} Property changes on: lucene/misc/src/java/org/apache/lucene/search/SwitchingSimilarityCollector.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/misc/src/java/org/apache/lucene/search/SwitchingSimilarity.java =================================================================== --- lucene/misc/src/java/org/apache/lucene/search/SwitchingSimilarity.java (revision 0) +++ lucene/misc/src/java/org/apache/lucene/search/SwitchingSimilarity.java (working copy) @@ -0,0 +1,132 @@ +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.AtomicReaderContext; +import org.apache.lucene.index.FieldInvertState; +import org.apache.lucene.search.similarities.Similarity; +import org.apache.lucene.util.BytesRef; + +/** When used along with {@link SwitchingSimilarityCollector}, a + * single query can be used to collect hits with multiple + * similarities. Note that only sims[0] is used to compute + * the norm, so all sims must "agree" on how the norm is + * computed. Note that to use this, you must + * make a new IndexSearcher per-request, because this class + * has state per query/hit. + * + * @lucene.experimental */ +public final class SwitchingSimilarity extends Similarity { + + /** the sub-similarities */ + private final Similarity sims[]; + + /** Which scorer we are currently scoring. */ + int scorerIndex; + + /** Creates a MultiSimilarity which will sum the scores + * of the provided sims. */ + public SwitchingSimilarity(Similarity... sims) { + this.sims = sims; + } + + @Override + public long computeNorm(FieldInvertState state) { + return sims[0].computeNorm(state); + } + + @Override + public SimWeight computeWeight(float queryBoost, CollectionStatistics collectionStats, TermStatistics... termStats) { + SimWeight subStats[] = new SimWeight[sims.length]; + for (int i = 0; i < subStats.length; i++) { + subStats[i] = sims[i].computeWeight(queryBoost, collectionStats, termStats); + } + return new SwitchingStats(subStats); + } + + @Override + public SimScorer simScorer(SimWeight stats, AtomicReaderContext context) throws IOException { + SimScorer subScorers[] = new SimScorer[sims.length]; + for (int i = 0; i < subScorers.length; i++) { + subScorers[i] = sims[i].simScorer(((SwitchingStats)stats).subStats[i], context); + } + return new SwitchingSimScorer(subScorers); + } + + /** Moves to the next sub-scorer. */ + public void switchScorer() { + scorerIndex++; + if (scorerIndex == sims.length) { + scorerIndex = 0; + } + } + + public class SwitchingSimScorer extends SimScorer { + private final SimScorer subScorers[]; + + SwitchingSimScorer(SimScorer subScorers[]) { + this.subScorers = subScorers; + } + + @Override + public float score(int doc, float freq) { + return subScorers[scorerIndex].score(doc, freq); + } + + @Override + public Explanation explain(int doc, Explanation freq) { + return subScorers[scorerIndex].explain(doc, freq); + } + + @Override + public float computeSlopFactor(int distance) { + return subScorers[0].computeSlopFactor(distance); + } + + @Override + public float computePayloadFactor(int doc, int start, int end, BytesRef payload) { + return subScorers[0].computePayloadFactor(doc, start, end, payload); + } + } + + static class SwitchingStats extends SimWeight { + final SimWeight subStats[]; + + SwitchingStats(SimWeight subStats[]) { + this.subStats = subStats; + } + + @Override + public float getValueForNormalization() { + float sum = 0.0f; + for (SimWeight stat : subStats) { + sum += stat.getValueForNormalization(); + } + return sum / subStats.length; + } + + @Override + public void normalize(float queryNorm, float topLevelBoost) { + for (SimWeight stat : subStats) { + stat.normalize(queryNorm, topLevelBoost); + } + } + } +} Property changes on: lucene/misc/src/java/org/apache/lucene/search/SwitchingSimilarity.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property