| 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.List; |
| |
| import org.apache.lucene.index.PostingsEnum; |
| import org.apache.lucene.search.similarities.Similarity; |
| |
| final class ExactPhraseScorer extends Scorer { |
| |
| private static class PostingsAndPosition { |
| private final PostingsEnum postings; |
| private final int offset; |
| private int freq, upTo, pos; |
| |
| public PostingsAndPosition(PostingsEnum postings, int offset) { |
| this.postings = postings; |
| this.offset = offset; |
| } |
| } |
| |
| private final ConjunctionDISI conjunction; |
| private final PostingsAndPosition[] postings; |
| |
| private int freq; |
| |
| private final Similarity.SimScorer docScorer; |
| private final boolean needsScores; |
| private float matchCost; |
| |
| ExactPhraseScorer(Weight weight, PhraseQuery.PostingsAndFreq[] postings, |
| Similarity.SimScorer docScorer, boolean needsScores, |
| float matchCost) throws IOException { |
| super(weight); |
| this.docScorer = docScorer; |
| this.needsScores = needsScores; |
| |
| List<DocIdSetIterator> iterators = new ArrayList<>(); |
| List<PostingsAndPosition> postingsAndPositions = new ArrayList<>(); |
| for(PhraseQuery.PostingsAndFreq posting : postings) { |
| iterators.add(posting.postings); |
| postingsAndPositions.add(new PostingsAndPosition(posting.postings, posting.position)); |
| } |
| conjunction = ConjunctionDISI.intersect(iterators); |
| this.postings = postingsAndPositions.toArray(new PostingsAndPosition[postingsAndPositions.size()]); |
| this.matchCost = matchCost; |
| } |
| |
| @Override |
| public TwoPhaseIterator asTwoPhaseIterator() { |
| return new TwoPhaseIterator(conjunction) { |
| @Override |
| public boolean matches() throws IOException { |
| return phraseFreq() > 0; |
| } |
| |
| @Override |
| public float matchCost() { |
| return matchCost; |
| } |
| }; |
| } |
| |
| private int doNext(int doc) throws IOException { |
| for (;; doc = conjunction.nextDoc()) { |
| if (doc == NO_MORE_DOCS || phraseFreq() > 0) { |
| return doc; |
| } |
| } |
| } |
| |
| @Override |
| public int nextDoc() throws IOException { |
| return doNext(conjunction.nextDoc()); |
| } |
| |
| @Override |
| public int advance(int target) throws IOException { |
| return doNext(conjunction.advance(target)); |
| } |
| |
| @Override |
| public String toString() { |
| return "ExactPhraseScorer(" + weight + ")"; |
| } |
| |
| @Override |
| public int freq() { |
| return freq; |
| } |
| |
| @Override |
| public int docID() { |
| return conjunction.docID(); |
| } |
| |
| @Override |
| public float score() { |
| return docScorer.score(docID(), freq); |
| } |
| |
| /** Advance the given pos enum to the first doc on or after {@code target}. |
| * Return {@code false} if the enum was exhausted before reaching |
| * {@code target} and {@code true} otherwise. */ |
| private static boolean advancePosition(PostingsAndPosition posting, int target) throws IOException { |
| while (posting.pos < target) { |
| if (posting.upTo == posting.freq) { |
| return false; |
| } else { |
| posting.pos = posting.postings.nextPosition(); |
| posting.upTo += 1; |
| } |
| } |
| return true; |
| } |
| |
| private int phraseFreq() throws IOException { |
| // reset state |
| final PostingsAndPosition[] postings = this.postings; |
| for (PostingsAndPosition posting : postings) { |
| posting.freq = posting.postings.freq(); |
| posting.pos = posting.postings.nextPosition(); |
| posting.upTo = 1; |
| } |
| |
| int freq = 0; |
| final PostingsAndPosition lead = postings[0]; |
| |
| advanceHead: |
| while (true) { |
| final int phrasePos = lead.pos - lead.offset; |
| for (int j = 1; j < postings.length; ++j) { |
| final PostingsAndPosition posting = postings[j]; |
| final int expectedPos = phrasePos + posting.offset; |
| |
| // advance up to the same position as the lead |
| if (advancePosition(posting, expectedPos) == false) { |
| break advanceHead; |
| } |
| |
| if (posting.pos != expectedPos) { // we advanced too far |
| if (advancePosition(lead, posting.pos - posting.offset + lead.offset)) { |
| continue advanceHead; |
| } else { |
| break advanceHead; |
| } |
| } |
| } |
| |
| freq += 1; |
| if (needsScores == false) { |
| break; |
| } |
| |
| if (lead.upTo == lead.freq) { |
| break; |
| } |
| lead.pos = lead.postings.nextPosition(); |
| lead.upTo += 1; |
| } |
| |
| return this.freq = freq; |
| } |
| |
| @Override |
| public long cost() { |
| return conjunction.cost(); |
| } |
| } |