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 = in.intersect(automaton, bytes); assert termsEnum != null; assert bytes == null || bytes.isValid(); 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 { assert result.isValid(); 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"; BytesRef ret = super.term(); assert ret == null || ret.isValid(); return ret; } @Override public void seekExact(long ord) throws IOException { super.seekExact(ord); state = State.POSITIONED; } @Override public SeekStatus seekCeil(BytesRef term) throws IOException { assert term.isValid(); SeekStatus result = super.seekCeil(term); if (result == SeekStatus.END) { state = State.UNPOSITIONED; } else { state = State.POSITIONED; } return result; } @Override public boolean seekExact(BytesRef text) throws IOException { assert text.isValid(); if (super.seekExact(text)) { 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 { assert term.isValid(); super.seekExact(term, state); this.state = State.POSITIONED; } } static enum DocsEnumState { START, ITERATING, FINISHED }; /** Wraps a docsenum with additional checks */ public static class AssertingDocsEnum extends FilterDocsEnum { private DocsEnumState state = DocsEnumState.START; private int doc; public AssertingDocsEnum(DocsEnum in) { this(in, true); } public AssertingDocsEnum(DocsEnum in, boolean failOnUnsupportedDocID) { super(in); try { int docid = in.docID(); assert docid == -1 : in.getClass() + ": invalid initial doc id: " + docid; } catch (UnsupportedOperationException e) { if (failOnUnsupportedDocID) { throw e; } } doc = -1; } @Override public int nextDoc() throws IOException { assert state != DocsEnumState.FINISHED : "nextDoc() called after NO_MORE_DOCS"; int nextDoc = super.nextDoc(); assert nextDoc > doc : "backwards nextDoc from " + doc + " to " + nextDoc + " " + in; if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) { state = DocsEnumState.FINISHED; } else { state = DocsEnumState.ITERATING; } assert super.docID() == nextDoc; return doc = nextDoc; } @Override public int advance(int target) throws IOException { assert state != DocsEnumState.FINISHED : "advance() called after NO_MORE_DOCS"; assert target > doc : "target must be > docID(), got " + target + " <= " + doc; int advanced = super.advance(target); assert advanced >= target : "backwards advance from: " + target + " to: " + advanced; if (advanced == DocIdSetIterator.NO_MORE_DOCS) { state = DocsEnumState.FINISHED; } else { state = DocsEnumState.ITERATING; } assert super.docID() == advanced; return doc = advanced; } @Override public int docID() { assert doc == super.docID() : " invalid docID() in " + in.getClass() + " " + super.docID() + " instead of " + doc; return doc; } @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; private int doc; public AssertingDocsAndPositionsEnum(DocsAndPositionsEnum in) { super(in); int docid = in.docID(); assert docid == -1 : "invalid initial doc id: " + docid; doc = -1; } @Override public int nextDoc() throws IOException { assert state != DocsEnumState.FINISHED : "nextDoc() called after NO_MORE_DOCS"; int nextDoc = super.nextDoc(); assert nextDoc > doc : "backwards nextDoc from " + doc + " to " + nextDoc; positionCount = 0; if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) { state = DocsEnumState.FINISHED; positionMax = 0; } else { state = DocsEnumState.ITERATING; positionMax = super.freq(); } assert super.docID() == nextDoc; return doc = nextDoc; } @Override public int advance(int target) throws IOException { assert state != DocsEnumState.FINISHED : "advance() called after NO_MORE_DOCS"; assert target > doc : "target must be > docID(), got " + target + " <= " + doc; int advanced = super.advance(target); 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(); } assert super.docID() == advanced; return doc = advanced; } @Override public int docID() { assert doc == super.docID() : " invalid docID() in " + in.getClass() + " " + super.docID() + " instead of " + doc; return doc; } @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.isValid() && payload.length > 0 : "getPayload() returned payload with invalid length!"; return payload; } } /** Wraps a NumericDocValues but with additional asserts */ public static class AssertingNumericDocValues extends NumericDocValues { private final NumericDocValues in; private final int maxDoc; public AssertingNumericDocValues(NumericDocValues in, int maxDoc) { this.in = in; this.maxDoc = maxDoc; } @Override public long get(int docID) { assert docID >= 0 && docID < maxDoc; return in.get(docID); } } /** Wraps a BinaryDocValues but with additional asserts */ public static class AssertingBinaryDocValues extends BinaryDocValues { private final BinaryDocValues in; private final int maxDoc; public AssertingBinaryDocValues(BinaryDocValues in, int maxDoc) { this.in = in; this.maxDoc = maxDoc; } @Override public void get(int docID, BytesRef result) { assert docID >= 0 && docID < maxDoc; assert result.isValid(); in.get(docID, result); assert result.isValid(); } } /** Wraps a SortedDocValues but with additional asserts */ public static class AssertingSortedDocValues extends SortedDocValues { private final SortedDocValues in; private final int maxDoc; private final int valueCount; public AssertingSortedDocValues(SortedDocValues in, int maxDoc) { this.in = in; this.maxDoc = maxDoc; this.valueCount = in.getValueCount(); assert valueCount >= 0 && valueCount <= maxDoc; } @Override public int getOrd(int docID) { assert docID >= 0 && docID < maxDoc; int ord = in.getOrd(docID); assert ord >= -1 && ord < valueCount; return ord; } @Override public void lookupOrd(int ord, BytesRef result) { assert ord >= 0 && ord < valueCount; assert result.isValid(); in.lookupOrd(ord, result); assert result.isValid(); } @Override public int getValueCount() { int valueCount = in.getValueCount(); assert valueCount == this.valueCount; // should not change return valueCount; } @Override public void get(int docID, BytesRef result) { assert docID >= 0 && docID < maxDoc; assert result.isValid(); in.get(docID, result); assert result.isValid(); } @Override public int lookupTerm(BytesRef key) { assert key.isValid(); int result = in.lookupTerm(key); assert result < valueCount; assert key.isValid(); return result; } } /** Wraps a SortedSetDocValues but with additional asserts */ public static class AssertingSortedSetDocValues extends SortedSetDocValues { private final SortedSetDocValues in; private final int maxDoc; private final long valueCount; long lastOrd = NO_MORE_ORDS; public AssertingSortedSetDocValues(SortedSetDocValues in, int maxDoc) { this.in = in; this.maxDoc = maxDoc; this.valueCount = in.getValueCount(); assert valueCount >= 0; } @Override public long nextOrd() { assert lastOrd != NO_MORE_ORDS; long ord = in.nextOrd(); assert ord < valueCount; assert ord == NO_MORE_ORDS || ord > lastOrd; lastOrd = ord; return ord; } @Override public void setDocument(int docID) { assert docID >= 0 && docID < maxDoc : "docid=" + docID + ",maxDoc=" + maxDoc; in.setDocument(docID); lastOrd = -2; } @Override public void lookupOrd(long ord, BytesRef result) { assert ord >= 0 && ord < valueCount; assert result.isValid(); in.lookupOrd(ord, result); assert result.isValid(); } @Override public long getValueCount() { long valueCount = in.getValueCount(); assert valueCount == this.valueCount; // should not change return valueCount; } @Override public long lookupTerm(BytesRef key) { assert key.isValid(); long result = in.lookupTerm(key); assert result < valueCount; assert key.isValid(); return result; } } @Override public NumericDocValues getNumericDocValues(String field) throws IOException { NumericDocValues dv = super.getNumericDocValues(field); FieldInfo fi = getFieldInfos().fieldInfo(field); if (dv != null) { assert fi != null; assert fi.getDocValuesType() == FieldInfo.DocValuesType.NUMERIC; return new AssertingNumericDocValues(dv, maxDoc()); } else { assert fi == null || fi.getDocValuesType() != FieldInfo.DocValuesType.NUMERIC; return null; } } @Override public BinaryDocValues getBinaryDocValues(String field) throws IOException { BinaryDocValues dv = super.getBinaryDocValues(field); FieldInfo fi = getFieldInfos().fieldInfo(field); if (dv != null) { assert fi != null; assert fi.getDocValuesType() == FieldInfo.DocValuesType.BINARY; return new AssertingBinaryDocValues(dv, maxDoc()); } else { assert fi == null || fi.getDocValuesType() != FieldInfo.DocValuesType.BINARY; return null; } } @Override public SortedDocValues getSortedDocValues(String field) throws IOException { SortedDocValues dv = super.getSortedDocValues(field); FieldInfo fi = getFieldInfos().fieldInfo(field); if (dv != null) { assert fi != null; assert fi.getDocValuesType() == FieldInfo.DocValuesType.SORTED; return new AssertingSortedDocValues(dv, maxDoc()); } else { assert fi == null || fi.getDocValuesType() != FieldInfo.DocValuesType.SORTED; return null; } } @Override public SortedSetDocValues getSortedSetDocValues(String field) throws IOException { SortedSetDocValues dv = super.getSortedSetDocValues(field); FieldInfo fi = getFieldInfos().fieldInfo(field); if (dv != null) { assert fi != null; assert fi.getDocValuesType() == FieldInfo.DocValuesType.SORTED_SET; return new AssertingSortedSetDocValues(dv, maxDoc()); } else { assert fi == null || fi.getDocValuesType() != FieldInfo.DocValuesType.SORTED_SET; return null; } } @Override public NumericDocValues getNormValues(String field) throws IOException { NumericDocValues dv = super.getNormValues(field); FieldInfo fi = getFieldInfos().fieldInfo(field); if (dv != null) { assert fi != null; assert fi.hasNorms(); return new AssertingNumericDocValues(dv, maxDoc()); } else { assert fi == null || fi.hasNorms() == false; return null; } } /** Wraps a Bits but with additional asserts */ public static class AssertingBits implements Bits { final Bits in; public AssertingBits(Bits in) { this.in = in; } @Override public boolean get(int index) { assert index >= 0 && index < length(); return in.get(index); } @Override public int length() { return in.length(); } } @Override public Bits getLiveDocs() { Bits liveDocs = super.getLiveDocs(); if (liveDocs != null) { assert maxDoc() == liveDocs.length(); liveDocs = new AssertingBits(liveDocs); } else { assert maxDoc() == numDocs(); assert !hasDeletions(); } return liveDocs; } @Override public Bits getDocsWithField(String field) throws IOException { Bits docsWithField = super.getDocsWithField(field); FieldInfo fi = getFieldInfos().fieldInfo(field); if (docsWithField != null) { assert fi != null; assert fi.hasDocValues(); assert maxDoc() == docsWithField.length(); docsWithField = new AssertingBits(docsWithField); } else { assert fi == null || fi.hasDocValues() == false; } return docsWithField; } // 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(); }