/* * 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.spans; import java.io.IOException; import java.util.List; /** * A Spans that is formed from the ordered subspans of a SpanNearQuery * where the subspans do not overlap and have a maximum slop between them. * <p> * The formed spans only contains minimum slop matches.<br> * The matching slop is computed from the distance(s) between * the non overlapping matching Spans.<br> * Successive matches are always formed from the successive Spans * of the SpanNearQuery. * <p> * The formed spans may contain overlaps when the slop is at least 1. * For example, when querying using * <pre>t1 t2 t3</pre> * with slop at least 1, the fragment: * <pre>t1 t2 t1 t3 t2 t3</pre> * matches twice: * <pre>t1 t2 .. t3 </pre> * <pre> t1 .. t2 t3</pre> * * Expert: * Only public for subclassing. Most implementations should not need this class */ public class NearSpansOrdered extends ConjunctionSpans { protected int matchStart = -1; protected int matchEnd = -1; protected int matchWidth = -1; private final int allowedSlop; public NearSpansOrdered(int allowedSlop, List<Spans> subSpans) throws IOException { super(subSpans); this.atFirstInCurrentDoc = true; // -1 startPosition/endPosition also at doc -1 this.allowedSlop = allowedSlop; } @Override boolean twoPhaseCurrentDocMatches() throws IOException { assert unpositioned(); oneExhaustedInCurrentDoc = false; while (subSpans[0].nextStartPosition() != NO_MORE_POSITIONS && !oneExhaustedInCurrentDoc) { if (stretchToOrder() && matchWidth <= allowedSlop) { return atFirstInCurrentDoc = true; } } return false; } private boolean unpositioned() { for (Spans span : subSpans) { if (span.startPosition() != -1) return false; } return true; } @Override public int nextStartPosition() throws IOException { if (atFirstInCurrentDoc) { atFirstInCurrentDoc = false; return matchStart; } oneExhaustedInCurrentDoc = false; while (subSpans[0].nextStartPosition() != NO_MORE_POSITIONS && !oneExhaustedInCurrentDoc) { if (stretchToOrder() && matchWidth <= allowedSlop) { return matchStart; } } return matchStart = matchEnd = NO_MORE_POSITIONS; } /** * Order the subSpans within the same document by using nextStartPosition on all subSpans * after the first as little as necessary. * Return true when the subSpans could be ordered in this way, * otherwise at least one is exhausted in the current doc. */ private boolean stretchToOrder() throws IOException { Spans prevSpans = subSpans[0]; matchStart = prevSpans.startPosition(); assert prevSpans.startPosition() != NO_MORE_POSITIONS : "prevSpans no start position "+prevSpans; assert prevSpans.endPosition() != NO_MORE_POSITIONS; matchWidth = 0; for (int i = 1; i < subSpans.length; i++) { Spans spans = subSpans[i]; assert spans.startPosition() != NO_MORE_POSITIONS; assert spans.endPosition() != NO_MORE_POSITIONS; if (advancePosition(spans, prevSpans.endPosition()) == NO_MORE_POSITIONS) { oneExhaustedInCurrentDoc = true; return false; } matchWidth += (spans.startPosition() - prevSpans.endPosition()); prevSpans = spans; } matchEnd = subSpans[subSpans.length - 1].endPosition(); return true; // all subSpans ordered and non overlapping } private static int advancePosition(Spans spans, int position) throws IOException { if (spans instanceof SpanNearQuery.GapSpans) { return ((SpanNearQuery.GapSpans)spans).skipToPosition(position); } while (spans.startPosition() < position) { spans.nextStartPosition(); } return spans.startPosition(); } @Override public int startPosition() { return atFirstInCurrentDoc ? -1 : matchStart; } @Override public int endPosition() { return atFirstInCurrentDoc ? -1 : matchEnd; } @Override public int width() { return matchWidth; } @Override public void collect(SpanCollector collector) throws IOException { for (Spans span : subSpans) { span.collect(collector); } } }