| /* |
| * 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 org.apache.lucene.search.DocIdSetIterator; |
| import org.apache.lucene.search.TwoPhaseIterator; |
| |
| /** |
| * Wraps a Spans with additional asserts |
| */ |
| class AssertingSpans extends Spans { |
| final Spans in; |
| int doc = -1; |
| |
| /** |
| * tracks current state of this spans |
| */ |
| static enum State { |
| /** |
| * document iteration has not yet begun ({@link #docID()} = -1) |
| */ |
| DOC_START, |
| |
| /** |
| * two-phase iterator has moved to a new docid, but {@link TwoPhaseIterator#matches()} has |
| * not been called or it returned false (so you should not do things with the enum) |
| */ |
| DOC_UNVERIFIED, |
| |
| /** |
| * iterator set to a valid docID, but position iteration has not yet begun ({@link #startPosition() == -1}) |
| */ |
| POS_START, |
| |
| /** |
| * iterator set to a valid docID, and positioned (-1 < {@link #startPosition()} < {@link #NO_MORE_POSITIONS}) |
| */ |
| ITERATING, |
| |
| /** |
| * positions exhausted ({@link #startPosition()} = {@link #NO_MORE_POSITIONS}) |
| */ |
| POS_FINISHED, |
| |
| /** |
| * documents exhausted ({@link #docID()} = {@link #NO_MORE_DOCS}) |
| */ |
| DOC_FINISHED |
| }; |
| |
| State state = State.DOC_START; |
| |
| AssertingSpans(Spans in) { |
| this.in = in; |
| } |
| |
| @Override |
| public int nextStartPosition() throws IOException { |
| assert state != State.DOC_START : "invalid position access, state=" + state + ": " + in; |
| assert state != State.DOC_FINISHED : "invalid position access, state=" + state + ": " + in; |
| assert state != State.DOC_UNVERIFIED : "invalid position access, state=" + state + ": " + in; |
| |
| checkCurrentPositions(); |
| |
| // move to next position |
| int prev = in.startPosition(); |
| int start = in.nextStartPosition(); |
| assert start >= prev : "invalid startPosition (positions went backwards, previous=" + prev + "): " + in; |
| |
| // transition state if necessary |
| if (start == NO_MORE_POSITIONS) { |
| state = State.POS_FINISHED; |
| } else { |
| state = State.ITERATING; |
| } |
| |
| // check new positions |
| checkCurrentPositions(); |
| return start; |
| } |
| |
| private void checkCurrentPositions() { |
| int start = in.startPosition(); |
| int end = in.endPosition(); |
| |
| if (state == State.DOC_START || state == State.DOC_UNVERIFIED || state == State.POS_START) { |
| assert start == -1 : "invalid startPosition (should be -1): " + in; |
| assert end == -1 : "invalid endPosition (should be -1): " + in; |
| } else if (state == State.POS_FINISHED) { |
| assert start == NO_MORE_POSITIONS : "invalid startPosition (should be NO_MORE_POSITIONS): " + in; |
| assert end == NO_MORE_POSITIONS : "invalid endPosition (should be NO_MORE_POSITIONS): " + in; |
| } else { |
| assert start >= 0 : "invalid startPosition (negative): " + in; |
| assert start <= end : "invalid startPosition (> endPosition): " + in; |
| } |
| } |
| |
| @Override |
| public int startPosition() { |
| checkCurrentPositions(); |
| return in.startPosition(); |
| } |
| |
| @Override |
| public int endPosition() { |
| checkCurrentPositions(); |
| return in.endPosition(); |
| } |
| |
| @Override |
| public int width() { |
| assert state == State.ITERATING; |
| final int distance = in.width(); |
| assert distance >= 0; |
| return distance; |
| } |
| |
| @Override |
| public void collect(SpanCollector collector) throws IOException { |
| assert state == State.ITERATING : "collect() called in illegal state: " + state + ": " + in; |
| in.collect(collector); |
| } |
| |
| @Override |
| public int docID() { |
| int doc = in.docID(); |
| assert doc == this.doc : "broken docID() impl: docID() = " + doc + ", but next/advance last returned: " + this.doc + ": " + in; |
| return doc; |
| } |
| |
| @Override |
| public int nextDoc() throws IOException { |
| assert state != State.DOC_FINISHED : "nextDoc() called after NO_MORE_DOCS: " + in; |
| int nextDoc = in.nextDoc(); |
| assert nextDoc > doc : "backwards nextDoc from " + doc + " to " + nextDoc + ": " + in; |
| if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) { |
| state = State.DOC_FINISHED; |
| } else { |
| assert in.startPosition() == -1 : "invalid initial startPosition() [should be -1]: " + in; |
| assert in.endPosition() == -1 : "invalid initial endPosition() [should be -1]: " + in; |
| state = State.POS_START; |
| } |
| doc = nextDoc; |
| return docID(); |
| } |
| |
| @Override |
| public int advance(int target) throws IOException { |
| assert state != State.DOC_FINISHED : "advance() called after NO_MORE_DOCS: " + in; |
| assert target > doc : "target must be > docID(), got " + target + " <= " + doc + ": " + in; |
| int advanced = in.advance(target); |
| assert advanced >= target : "backwards advance from: " + target + " to: " + advanced + ": " + in; |
| if (advanced == DocIdSetIterator.NO_MORE_DOCS) { |
| state = State.DOC_FINISHED; |
| } else { |
| assert in.startPosition() == -1 : "invalid initial startPosition() [should be -1]: " + in; |
| assert in.endPosition() == -1 : "invalid initial endPosition() [should be -1]: " + in; |
| state = State.POS_START; |
| } |
| doc = advanced; |
| return docID(); |
| } |
| |
| @Override |
| public String toString() { |
| return "Asserting(" + in + ")"; |
| } |
| |
| @Override |
| public long cost() { |
| return in.cost(); |
| } |
| |
| @Override |
| public float positionsCost() { |
| float cost = in.positionsCost(); |
| assert ! Float.isNaN(cost) : "positionsCost() should not be NaN"; |
| assert cost > 0 : "positionsCost() must be positive"; |
| return cost; |
| } |
| |
| @Override |
| public TwoPhaseIterator asTwoPhaseIterator() { |
| final TwoPhaseIterator iterator = in.asTwoPhaseIterator(); |
| if (iterator == null) { |
| return null; |
| } |
| return new AssertingTwoPhaseView(iterator); |
| } |
| |
| class AssertingTwoPhaseView extends TwoPhaseIterator { |
| final TwoPhaseIterator in; |
| int lastDoc = -1; |
| |
| AssertingTwoPhaseView(TwoPhaseIterator iterator) { |
| super(new AssertingDISI(iterator.approximation())); |
| this.in = iterator; |
| } |
| |
| @Override |
| public boolean matches() throws IOException { |
| if (approximation.docID() == -1 || approximation.docID() == DocIdSetIterator.NO_MORE_DOCS) { |
| throw new AssertionError("matches() should not be called on doc ID " + approximation.docID()); |
| } |
| if (lastDoc == approximation.docID()) { |
| throw new AssertionError("matches() has been called twice on doc ID " + approximation.docID()); |
| } |
| lastDoc = approximation.docID(); |
| boolean v = in.matches(); |
| if (v) { |
| state = State.POS_START; |
| } |
| return v; |
| } |
| |
| @Override |
| public float matchCost() { |
| float cost = in.matchCost(); |
| if (Float.isNaN(cost)) { |
| throw new AssertionError("matchCost()=" + cost + " should not be NaN on doc ID " + approximation.docID()); |
| } |
| if (cost < 0) { |
| throw new AssertionError("matchCost()=" + cost + " should be non negative on doc ID " + approximation.docID()); |
| } |
| return cost; |
| } |
| } |
| |
| class AssertingDISI extends DocIdSetIterator { |
| final DocIdSetIterator in; |
| |
| AssertingDISI(DocIdSetIterator in) { |
| this.in = in; |
| } |
| |
| @Override |
| public int docID() { |
| assert in.docID() == AssertingSpans.this.docID(); |
| return in.docID(); |
| } |
| |
| @Override |
| public int nextDoc() throws IOException { |
| assert state != State.DOC_FINISHED : "nextDoc() called after NO_MORE_DOCS: " + in; |
| int nextDoc = in.nextDoc(); |
| assert nextDoc > doc : "backwards nextDoc from " + doc + " to " + nextDoc + ": " + in; |
| if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) { |
| state = State.DOC_FINISHED; |
| } else { |
| state = State.DOC_UNVERIFIED; |
| } |
| doc = nextDoc; |
| return docID(); |
| } |
| |
| @Override |
| public int advance(int target) throws IOException { |
| assert state != State.DOC_FINISHED : "advance() called after NO_MORE_DOCS: " + in; |
| assert target > doc : "target must be > docID(), got " + target + " <= " + doc + ": " + in; |
| int advanced = in.advance(target); |
| assert advanced >= target : "backwards advance from: " + target + " to: " + advanced + ": " + in; |
| if (advanced == DocIdSetIterator.NO_MORE_DOCS) { |
| state = State.DOC_FINISHED; |
| } else { |
| state = State.DOC_UNVERIFIED; |
| } |
| doc = advanced; |
| return docID(); |
| } |
| |
| @Override |
| public long cost() { |
| return in.cost(); |
| } |
| } |
| } |