package org.apache.lucene.index; import java.io.IOException; import java.util.Iterator; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.Bits; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.automaton.CompiledAutomaton; /* * 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. */ /** * A {@link FilterAtomicReader} that can be used to apply * additional checks for tests. */ public class AssertingAtomicReader extends FilterAtomicReader { public AssertingAtomicReader(AtomicReader in) { super(in); // check some basic reader sanity assert in.maxDoc() >= 0; assert in.numDocs() <= in.maxDoc(); assert in.numDeletedDocs() + in.numDocs() == in.maxDoc(); assert !in.hasDeletions() || in.numDeletedDocs() > 0 && in.numDocs() < in.maxDoc(); } @Override public Fields fields() throws IOException { Fields fields = super.fields(); return fields == null ? null : new AssertingFields(fields); } @Override public Fields getTermVectors(int docID) throws IOException { Fields fields = super.getTermVectors(docID); return fields == null ? null : new AssertingFields(fields); } /** * Wraps a Fields but with additional asserts */ public static class AssertingFields extends FilterFields { public AssertingFields(Fields in) { super(in); } @Override public Iterator<String> iterator() { Iterator<String> iterator = super.iterator(); assert iterator != null; return iterator; } @Override public Terms terms(String field) throws IOException { Terms terms = super.terms(field); return terms == null ? null : new AssertingTerms(terms); } } /** * Wraps a Terms but with additional asserts */ public static class AssertingTerms extends FilterTerms { public AssertingTerms(Terms in) { super(in); } @Override public TermsEnum intersect(CompiledAutomaton automaton, BytesRef bytes) throws IOException { TermsEnum termsEnum = super.intersect(automaton, bytes); assert termsEnum != null; return new AssertingTermsEnum(termsEnum); } @Override public TermsEnum iterator(TermsEnum reuse) throws IOException { // TODO: should we give this thing a random to be super-evil, // and randomly *not* unwrap? if (reuse instanceof AssertingTermsEnum) { reuse = ((AssertingTermsEnum) reuse).in; } TermsEnum termsEnum = super.iterator(reuse); assert termsEnum != null; return new AssertingTermsEnum(termsEnum); } } static class AssertingTermsEnum extends FilterTermsEnum { private enum State {INITIAL, POSITIONED, UNPOSITIONED}; private State state = State.INITIAL; public AssertingTermsEnum(TermsEnum in) { super(in); } @Override public DocsEnum docs(Bits liveDocs, DocsEnum reuse, int flags) throws IOException { assert state == State.POSITIONED: "docs(...) called on unpositioned TermsEnum"; // TODO: should we give this thing a random to be super-evil, // and randomly *not* unwrap? if (reuse instanceof AssertingDocsEnum) { reuse = ((AssertingDocsEnum) reuse).in; } DocsEnum docs = super.docs(liveDocs, reuse, flags); return docs == null ? null : new AssertingDocsEnum(docs); } @Override public DocsAndPositionsEnum docsAndPositions(Bits liveDocs, DocsAndPositionsEnum reuse, int flags) throws IOException { assert state == State.POSITIONED: "docsAndPositions(...) called on unpositioned TermsEnum"; // TODO: should we give this thing a random to be super-evil, // and randomly *not* unwrap? if (reuse instanceof AssertingDocsAndPositionsEnum) { reuse = ((AssertingDocsAndPositionsEnum) reuse).in; } DocsAndPositionsEnum docs = super.docsAndPositions(liveDocs, reuse, flags); return docs == null ? null : new AssertingDocsAndPositionsEnum(docs); } // TODO: we should separately track if we are 'at the end' ? // someone should not call next() after it returns null!!!! @Override public BytesRef next() throws IOException { assert state == State.INITIAL || state == State.POSITIONED: "next() called on unpositioned TermsEnum"; BytesRef result = super.next(); if (result == null) { state = State.UNPOSITIONED; } else { state = State.POSITIONED; } return result; } @Override public long ord() throws IOException { assert state == State.POSITIONED : "ord() called on unpositioned TermsEnum"; return super.ord(); } @Override public int docFreq() throws IOException { assert state == State.POSITIONED : "docFreq() called on unpositioned TermsEnum"; return super.docFreq(); } @Override public long totalTermFreq() throws IOException { assert state == State.POSITIONED : "totalTermFreq() called on unpositioned TermsEnum"; return super.totalTermFreq(); } @Override public BytesRef term() throws IOException { assert state == State.POSITIONED : "term() called on unpositioned TermsEnum"; return super.term(); } @Override public void seekExact(long ord) throws IOException { super.seekExact(ord); state = State.POSITIONED; } @Override public SeekStatus seekCeil(BytesRef term, boolean useCache) throws IOException { SeekStatus result = super.seekCeil(term, useCache); if (result == SeekStatus.END) { state = State.UNPOSITIONED; } else { state = State.POSITIONED; } return result; } @Override public boolean seekExact(BytesRef text, boolean useCache) throws IOException { if (super.seekExact(text, useCache)) { state = State.POSITIONED; return true; } else { state = State.UNPOSITIONED; return false; } } @Override public TermState termState() throws IOException { assert state == State.POSITIONED : "termState() called on unpositioned TermsEnum"; return super.termState(); } @Override public void seekExact(BytesRef term, TermState state) throws IOException { super.seekExact(term, state); this.state = State.POSITIONED; } } static enum DocsEnumState { START, ITERATING, FINISHED }; static class AssertingDocsEnum extends FilterDocsEnum { private DocsEnumState state = DocsEnumState.START; public AssertingDocsEnum(DocsEnum in) { super(in); int docid = in.docID(); assert docid == -1 || docid == DocIdSetIterator.NO_MORE_DOCS : "invalid initial doc id: " + docid; } @Override public int nextDoc() throws IOException { assert state != DocsEnumState.FINISHED : "nextDoc() called after NO_MORE_DOCS"; int nextDoc = super.nextDoc(); assert nextDoc >= 0 : "invalid doc id: " + nextDoc; if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) { state = DocsEnumState.FINISHED; } else { state = DocsEnumState.ITERATING; } return nextDoc; } @Override public int advance(int target) throws IOException { assert state != DocsEnumState.FINISHED : "advance() called after NO_MORE_DOCS"; int advanced = super.advance(target); assert advanced >= 0 : "invalid doc id: " + advanced; assert advanced >= target : "backwards advance from: " + target + " to: " + advanced; if (advanced == DocIdSetIterator.NO_MORE_DOCS) { state = DocsEnumState.FINISHED; } else { state = DocsEnumState.ITERATING; } return advanced; } // NOTE: We don't assert anything for docId(). Specifically DocsEnum javadocs // are ambiguous with DocIdSetIterator here, DocIdSetIterator says its ok // to call this method before nextDoc(), just that it must be -1 or NO_MORE_DOCS! @Override public int freq() throws IOException { assert state != DocsEnumState.START : "freq() called before nextDoc()/advance()"; assert state != DocsEnumState.FINISHED : "freq() called after NO_MORE_DOCS"; int freq = super.freq(); assert freq > 0; return freq; } } static class AssertingDocsAndPositionsEnum extends FilterDocsAndPositionsEnum { private DocsEnumState state = DocsEnumState.START; private int positionMax = 0; private int positionCount = 0; public AssertingDocsAndPositionsEnum(DocsAndPositionsEnum in) { super(in); int docid = in.docID(); assert docid == -1 || docid == DocIdSetIterator.NO_MORE_DOCS : "invalid initial doc id: " + docid; } @Override public int nextDoc() throws IOException { assert state != DocsEnumState.FINISHED : "nextDoc() called after NO_MORE_DOCS"; int nextDoc = super.nextDoc(); assert nextDoc >= 0 : "invalid doc id: " + nextDoc; positionCount = 0; if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) { state = DocsEnumState.FINISHED; positionMax = 0; } else { state = DocsEnumState.ITERATING; positionMax = super.freq(); } return nextDoc; } @Override public int advance(int target) throws IOException { assert state != DocsEnumState.FINISHED : "advance() called after NO_MORE_DOCS"; int advanced = super.advance(target); assert advanced >= 0 : "invalid doc id: " + advanced; assert advanced >= target : "backwards advance from: " + target + " to: " + advanced; positionCount = 0; if (advanced == DocIdSetIterator.NO_MORE_DOCS) { state = DocsEnumState.FINISHED; positionMax = 0; } else { state = DocsEnumState.ITERATING; positionMax = super.freq(); } return advanced; } @Override public int freq() throws IOException { assert state != DocsEnumState.START : "freq() called before nextDoc()/advance()"; assert state != DocsEnumState.FINISHED : "freq() called after NO_MORE_DOCS"; int freq = super.freq(); assert freq > 0; return freq; } @Override public int nextPosition() throws IOException { assert state != DocsEnumState.START : "nextPosition() called before nextDoc()/advance()"; assert state != DocsEnumState.FINISHED : "nextPosition() called after NO_MORE_DOCS"; assert positionCount < positionMax : "nextPosition() called more than freq() times!"; int position = super.nextPosition(); assert position >= 0 || position == -1 : "invalid position: " + position; positionCount++; return position; } @Override public int startOffset() throws IOException { assert state != DocsEnumState.START : "startOffset() called before nextDoc()/advance()"; assert state != DocsEnumState.FINISHED : "startOffset() called after NO_MORE_DOCS"; assert positionCount > 0 : "startOffset() called before nextPosition()!"; return super.startOffset(); } @Override public int endOffset() throws IOException { assert state != DocsEnumState.START : "endOffset() called before nextDoc()/advance()"; assert state != DocsEnumState.FINISHED : "endOffset() called after NO_MORE_DOCS"; assert positionCount > 0 : "endOffset() called before nextPosition()!"; return super.endOffset(); } @Override public BytesRef getPayload() throws IOException { assert state != DocsEnumState.START : "getPayload() called before nextDoc()/advance()"; assert state != DocsEnumState.FINISHED : "getPayload() called after NO_MORE_DOCS"; assert positionCount > 0 : "getPayload() called before nextPosition()!"; BytesRef payload = super.getPayload(); assert payload == null || payload.length > 0 : "getPayload() returned payload with invalid length!"; return payload; } } // this is the same hack as FCInvisible @Override public Object getCoreCacheKey() { return cacheKey; } @Override public Object getCombinedCoreAndDeletesKey() { return cacheKey; } private final Object cacheKey = new Object(); }