| /* |
| * 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. |
| */ |
| package org.apache.lucene.search; |
| |
| |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.Collections; |
| |
| /** A Scorer for queries with a required subscorer |
| * and an excluding (prohibited) sub {@link Scorer}. |
| */ |
| class ReqExclScorer extends Scorer { |
| |
| private final Scorer reqScorer; |
| // approximations of the scorers, or the scorers themselves if they don't support approximations |
| private final DocIdSetIterator reqApproximation; |
| private final DocIdSetIterator exclApproximation; |
| // two-phase views of the scorers, or null if they do not support approximations |
| private final TwoPhaseIterator reqTwoPhaseIterator; |
| private final TwoPhaseIterator exclTwoPhaseIterator; |
| |
| /** Construct a <code>ReqExclScorer</code>. |
| * @param reqScorer The scorer that must match, except where |
| * @param exclScorer indicates exclusion. |
| */ |
| public ReqExclScorer(Scorer reqScorer, Scorer exclScorer) { |
| super(reqScorer.weight); |
| this.reqScorer = reqScorer; |
| reqTwoPhaseIterator = reqScorer.twoPhaseIterator(); |
| if (reqTwoPhaseIterator == null) { |
| reqApproximation = reqScorer.iterator(); |
| } else { |
| reqApproximation = reqTwoPhaseIterator.approximation(); |
| } |
| exclTwoPhaseIterator = exclScorer.twoPhaseIterator(); |
| if (exclTwoPhaseIterator == null) { |
| exclApproximation = exclScorer.iterator(); |
| } else { |
| exclApproximation = exclTwoPhaseIterator.approximation(); |
| } |
| } |
| |
| /** Confirms whether or not the given {@link TwoPhaseIterator} |
| * matches on the current document. */ |
| private static boolean matchesOrNull(TwoPhaseIterator it) throws IOException { |
| return it == null || it.matches(); |
| } |
| |
| @Override |
| public DocIdSetIterator iterator() { |
| return TwoPhaseIterator.asDocIdSetIterator(twoPhaseIterator()); |
| } |
| |
| @Override |
| public int docID() { |
| return reqApproximation.docID(); |
| } |
| |
| @Override |
| public float score() throws IOException { |
| return reqScorer.score(); // reqScorer may be null when next() or skipTo() already return false |
| } |
| |
| @Override |
| public int advanceShallow(int target) throws IOException { |
| return reqScorer.advanceShallow(target); |
| } |
| |
| @Override |
| public float getMaxScore(int upTo) throws IOException { |
| return reqScorer.getMaxScore(upTo); |
| } |
| |
| @Override |
| public void setMinCompetitiveScore(float score) throws IOException { |
| // The score of this scorer is the same as the score of 'reqScorer'. |
| reqScorer.setMinCompetitiveScore(score); |
| } |
| |
| @Override |
| public Collection<ChildScorable> getChildren() { |
| return Collections.singleton(new ChildScorable(reqScorer, "MUST")); |
| } |
| |
| /** |
| * Estimation of the number of operations required to call DISI.advance. |
| * This is likely completely wrong, especially given that the cost of |
| * this method usually depends on how far you want to advance, but it's |
| * probably better than nothing. |
| */ |
| private static final int ADVANCE_COST = 10; |
| |
| private static float matchCost( |
| DocIdSetIterator reqApproximation, |
| TwoPhaseIterator reqTwoPhaseIterator, |
| DocIdSetIterator exclApproximation, |
| TwoPhaseIterator exclTwoPhaseIterator) { |
| float matchCost = 2; // we perform 2 comparisons to advance exclApproximation |
| if (reqTwoPhaseIterator != null) { |
| // this two-phase iterator must always be matched |
| matchCost += reqTwoPhaseIterator.matchCost(); |
| } |
| |
| // match cost of the prohibited clause: we need to advance the approximation |
| // and match the two-phased iterator |
| final float exclMatchCost = ADVANCE_COST |
| + (exclTwoPhaseIterator == null ? 0 : exclTwoPhaseIterator.matchCost()); |
| |
| // upper value for the ratio of documents that reqApproximation matches that |
| // exclApproximation also matches |
| float ratio; |
| if (reqApproximation.cost() <= 0) { |
| ratio = 1f; |
| } else if (exclApproximation.cost() <= 0) { |
| ratio = 0f; |
| } else { |
| ratio = (float) Math.min(reqApproximation.cost(), exclApproximation.cost()) / reqApproximation.cost(); |
| } |
| matchCost += ratio * exclMatchCost; |
| |
| return matchCost; |
| } |
| |
| @Override |
| public TwoPhaseIterator twoPhaseIterator() { |
| final float matchCost = matchCost(reqApproximation, reqTwoPhaseIterator, exclApproximation, exclTwoPhaseIterator); |
| |
| if (reqTwoPhaseIterator == null |
| || (exclTwoPhaseIterator != null && reqTwoPhaseIterator.matchCost() <= exclTwoPhaseIterator.matchCost())) { |
| // reqTwoPhaseIterator is LESS costly than exclTwoPhaseIterator, check it first |
| return new TwoPhaseIterator(reqApproximation) { |
| |
| @Override |
| public boolean matches() throws IOException { |
| final int doc = reqApproximation.docID(); |
| // check if the doc is not excluded |
| int exclDoc = exclApproximation.docID(); |
| if (exclDoc < doc) { |
| exclDoc = exclApproximation.advance(doc); |
| } |
| if (exclDoc != doc) { |
| return matchesOrNull(reqTwoPhaseIterator); |
| } |
| return matchesOrNull(reqTwoPhaseIterator) && !matchesOrNull(exclTwoPhaseIterator); |
| } |
| |
| @Override |
| public float matchCost() { |
| return matchCost; |
| } |
| }; |
| } else { |
| // reqTwoPhaseIterator is MORE costly than exclTwoPhaseIterator, check it last |
| return new TwoPhaseIterator(reqApproximation) { |
| |
| @Override |
| public boolean matches() throws IOException { |
| final int doc = reqApproximation.docID(); |
| // check if the doc is not excluded |
| int exclDoc = exclApproximation.docID(); |
| if (exclDoc < doc) { |
| exclDoc = exclApproximation.advance(doc); |
| } |
| if (exclDoc != doc) { |
| return matchesOrNull(reqTwoPhaseIterator); |
| } |
| return !matchesOrNull(exclTwoPhaseIterator) && matchesOrNull(reqTwoPhaseIterator); |
| } |
| |
| @Override |
| public float matchCost() { |
| return matchCost; |
| } |
| }; |
| } |
| } |
| } |