package doser.lucene.query; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Weight; import org.apache.lucene.util.ArrayUtil; /** Scorer for conjunctions, sets of queries, all of which are required. */ class ConjunctionScorer extends Scorer { static final class DocsAndFreqs { final long cost; int doc = -1; final Scorer scorer; DocsAndFreqs(final Scorer scorer) { this.scorer = scorer; cost = scorer.cost(); } } private final LearnToRankClause[] clauses; private final float coord; private final int docBase; protected final DocsAndFreqs[] docsAndFreqs; protected int lastDoc = -1; private final DocsAndFreqs lead; ConjunctionScorer(final Weight weight, final Scorer[] scorers, final float coord, final LearnToRankClause[] ltrclauses, final int docBase) { super(weight); this.coord = coord; this.docBase = docBase; clauses = ltrclauses; docsAndFreqs = new DocsAndFreqs[scorers.length]; for (int i = 0; i < scorers.length; i++) { docsAndFreqs[i] = new DocsAndFreqs(scorers[i]); } // Sort the array the first time to allow the least frequent DocsEnum to // lead the matching. ArrayUtil.timSort(docsAndFreqs, new Comparator<DocsAndFreqs>() { @Override public int compare(final DocsAndFreqs obj1, final DocsAndFreqs obj2) { return Long.signum(obj1.cost - obj2.cost); } }); lead = docsAndFreqs[0]; // least frequent DocsEnum leads the // intersection } ConjunctionScorer(final Weight weight, final Scorer[] scorers, final LearnToRankClause[] ltrclauses, final int docBase) { this(weight, scorers, 1f, ltrclauses, docBase); } @Override public int advance(final int target) throws IOException { lead.doc = lead.scorer.advance(target); return lastDoc = doNext(lead.doc); } @Override public long cost() { return lead.scorer.cost(); } @Override public int docID() { return lastDoc; } private int doNext(int doc) throws IOException { // NOPMD by quh on 28.02.14 // 10:45 for (;;) { // doc may already be NO_MORE_DOCS here, but we don't check // explicitly // since all scorers should advance to NO_MORE_DOCS, match, then // return that value. advanceHead: for (;;) { for (int i = 1; i < docsAndFreqs.length; i++) { // invariant: docsAndFreqs[i].doc <= doc at this point. // docsAndFreqs[i].doc may already be equal to doc if we // "broke advanceHead" // on the previous iteration and the advance on the lead // scorer exactly matched. if (docsAndFreqs[i].doc < doc) { docsAndFreqs[i].doc = docsAndFreqs[i].scorer .advance(doc); if (docsAndFreqs[i].doc > doc) { // DocsEnum beyond the current doc - break and // advance lead to the new highest doc. doc = docsAndFreqs[i].doc; break advanceHead; } } } // success - all DocsEnums are on the same doc return doc; } // advance head for next iteration doc = lead.doc = lead.scorer.advance(doc); } } @Override public int freq() { return docsAndFreqs.length; } @Override public Collection<ChildScorer> getChildren() { final ArrayList<ChildScorer> children = new ArrayList<ChildScorer>( docsAndFreqs.length); for (final DocsAndFreqs docs : docsAndFreqs) { children.add(new ChildScorer(docs.scorer, "MUST")); } return children; } @Override public int nextDoc() throws IOException { lead.doc = lead.scorer.nextDoc(); return lastDoc = doNext(lead.doc); } @Override public float score() throws IOException { // TODO: sum into a double and cast to float if we ever send required // clauses to BS1 float sum = 0.0f; for (int i = 0; i < docsAndFreqs.length; i++) { final float val = docsAndFreqs[i].scorer.score() * clauses[i].getWeight(); sum += val; clauses[i].addFeatureValue(docBase, lastDoc, val); } return sum * coord; } }