/** * Copyright 2014 National University of Ireland, Galway. * * This file is part of the SIREn project. Project and contact information: * * https://github.com/rdelbru/SIREn * * Licensed 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.sindice.siren.search.node; import java.io.IOException; import org.apache.lucene.util.IntsRef; import org.sindice.siren.util.NodeUtils; /** * A {@link NodeScorer} for queries within a node with a required subscorer * and an excluding (prohibited) subscorer. * * <p> * * Only nodes matching the required subscorer and not matching the prohibited * subscorer are kept. * * <p> * * Code taken from {@link ReqExclScorer} and adapted for the Siren use * case. */ class NodeReqExclScorer extends NodeScorer { /** * The required and excluded primitive Siren scorers. */ private final NodeScorer reqScorer; private NodeScorer exclScorer; /** * Construct a {@link NodeReqExclScorer}. * * @param reqScorer * The scorer that must match, except where * @param exclScorer * indicates exclusion. */ public NodeReqExclScorer(final NodeScorer reqScorer, final NodeScorer exclScorer) { super(reqScorer.getWeight()); this.reqScorer = reqScorer; this.exclScorer = exclScorer; } @Override public boolean nextCandidateDocument() throws IOException { if (!reqScorer.nextCandidateDocument()) { return false; } if (exclScorer == null) { return true; // reqScorer.nextCandidateDocument() already returned true } return this.toNonExcludedCandidateDocument(); } /** * Advance to non excluded candidate document. <br> * On entry: * <ul> * <li>reqScorer != null, * <li>exclScorer != null, * <li>reqScorer was advanced once via {@link #nextCandidateDocument()} or * {@link #skipToCandidate(int)} and reqScorer.doc() may still be excluded. * </ul> * Advances reqScorer a non excluded candidate document, if any. * <p> * If reqScorer.doc() is equal to exclScorer.doc(), reqScorer.doc() cannot * be excluded immediately, i.e., it is a valid candidate document. We have to * check the exclusion of reqScorer.node(). * * @return true iff there is a non excluded candidate document. */ private boolean toNonExcludedCandidateDocument() throws IOException { do { int exclDoc = exclScorer.doc(); final int reqDoc = reqScorer.doc(); // may be excluded if (reqDoc <= exclDoc) { return true; // reqScorer advanced to or before exclScorer, not excluded } else if (reqDoc > exclDoc) { if (!exclScorer.skipToCandidate(reqDoc)) { exclScorer = null; // exhausted, no more exclusions return true; } exclDoc = exclScorer.doc(); if (exclDoc >= reqDoc) { return true; // exclScorer advanced to or before reqScorer, not excluded } } } while (reqScorer.nextCandidateDocument()); return false; } @Override public boolean nextNode() throws IOException { if (!reqScorer.nextNode()) { // Move to the next matching node return false; // exhausted, nothing left } if (exclScorer == null || exclScorer.doc() != reqScorer.doc()) { return true; // reqScorer.nextNode() already returned true } // reqScorer and exclScorer are positioned on the same candidate document return this.toNonExcludedNode(); } /** * Advance to an excluded cell. <br> * On entry: * <ul> * <li>reqScorer != null, * <li>exclScorer != null, * <li>reqScorer and exclScorer were advanced once via * {@link #nextCandidateDocument()} or {@link #skipToCandidate(int)} and were * positioned on the same candidate document number * <li> reqScorer.doc() and reqScorer.node() may still be excluded. * </ul> * Advances reqScorer to the next non excluded required node, if any. * * @return true iff the current candidate document has a non excluded required * node. */ private boolean toNonExcludedNode() throws IOException { IntsRef reqNode = reqScorer.node(); // may be excluded IntsRef exclNode = exclScorer.node(); int comparison; while ((comparison = NodeUtils.compare(reqNode, exclNode)) >= 0) { // if node equal, advance to next node in reqScorer if (comparison == 0 && !reqScorer.nextNode()) { return false; } // if node equal or excluded node ancestor, advance to next node if (!exclScorer.nextNode()) { return true; } reqNode = reqScorer.node(); exclNode = exclScorer.node(); } return true; } @Override public int doc() { return reqScorer.doc(); // reqScorer may be null when next() or skipTo() // already return false } @Override public IntsRef node() { return reqScorer.node(); } @Override public float freqInNode() throws IOException { return reqScorer.freqInNode(); } @Override public float scoreInNode() throws IOException { return reqScorer.scoreInNode(); // reqScorer may be null when next() or skipTo() // already return false } @Override public boolean skipToCandidate(final int target) throws IOException { if (exclScorer == null) { return reqScorer.skipToCandidate(target); } if (!reqScorer.skipToCandidate(target)) { return false; } return this.toNonExcludedCandidateDocument(); } @Override public String toString() { return "NodeReqExclScorer(" + weight + "," + this.doc() + "," + this.node() + ")"; } }