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.ArrayList; import java.util.Collection; import java.util.Comparator; import org.apache.lucene.util.ArrayUtil; /** Scorer for conjunctions, sets of queries, all of which are required. */ class ConjunctionScorer extends Scorer { protected int lastDoc = -1; protected final DocsAndFreqs[] docsAndFreqs; private final DocsAndFreqs lead; private final float coord; ConjunctionScorer(Weight weight, Scorer[] scorers) { this(weight, scorers, 1f); } ConjunctionScorer(Weight weight, Scorer[] scorers, float coord) { super(weight); this.coord = coord; this.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(DocsAndFreqs o1, DocsAndFreqs o2) { return Long.compare(o1.cost, o2.cost); } }); lead = docsAndFreqs[0]; // least frequent DocsEnum leads the intersection } private int doNext(int doc) throws IOException { 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 advance(int target) throws IOException { lead.doc = lead.scorer.advance(target); return lastDoc = doNext(lead.doc); } @Override public int docID() { return lastDoc; } @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 (DocsAndFreqs docs : docsAndFreqs) { sum += docs.scorer.score(); } return sum * coord; } @Override public int freq() { return docsAndFreqs.length; } @Override public long cost() { return lead.scorer.cost(); } @Override public Collection<ChildScorer> getChildren() { ArrayList<ChildScorer> children = new ArrayList<>(docsAndFreqs.length); for (DocsAndFreqs docs : docsAndFreqs) { children.add(new ChildScorer(docs.scorer, "MUST")); } return children; } static final class DocsAndFreqs { final long cost; final Scorer scorer; int doc = -1; DocsAndFreqs(Scorer scorer) { this.scorer = scorer; this.cost = scorer.cost(); } } }