/* * 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.solr.uninverting; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.lucene.codecs.PostingsFormat; // javadocs import org.apache.lucene.index.DocValues; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LegacySortedSetDocValues; import org.apache.lucene.index.LegacySortedSetDocValuesWrapper; import org.apache.lucene.index.PostingsEnum; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.index.Terms; import org.apache.lucene.index.TermsEnum; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.Accountable; import org.apache.lucene.util.Bits; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.PagedBytes; import org.apache.lucene.util.StringHelper; /** * This class enables fast access to multiple term ords for * a specified field across all docIDs. * * Like FieldCache, it uninverts the index and holds a * packed data structure in RAM to enable fast access. * Unlike FieldCache, it can handle multi-valued fields, * and, it does not hold the term bytes in RAM. Rather, you * must obtain a TermsEnum from the {@link #getOrdTermsEnum} * method, and then seek-by-ord to get the term's bytes. * * While normally term ords are type long, in this API they are * int as the internal representation here cannot address * more than MAX_INT unique terms. Also, typically this * class is used on fields with relatively few unique terms * vs the number of documents. In addition, there is an * internal limit (16 MB) on how many bytes each chunk of * documents may consume. If you trip this limit you'll hit * an IllegalStateException. * * Deleted documents are skipped during uninversion, and if * you look them up you'll get 0 ords. * * The returned per-document ords do not retain their * original order in the document. Instead they are returned * in sorted (by ord, ie term's BytesRef comparator) order. They * are also de-dup'd (ie if doc has same term more than once * in this field, you'll only get that ord back once). * * This class * will create its own term index internally, allowing to * create a wrapped TermsEnum that can handle ord. The * {@link #getOrdTermsEnum} method then provides this * wrapped enum. * * The RAM consumption of this class can be high! * * @lucene.experimental */ /* * Final form of the un-inverted field: * Each document points to a list of term numbers that are contained in that document. * * Term numbers are in sorted order, and are encoded as variable-length deltas from the * previous term number. Real term numbers start at 2 since 0 and 1 are reserved. A * term number of 0 signals the end of the termNumber list. * * There is a single int[maxDoc()] which either contains a pointer into a byte[] for * the termNumber lists, or directly contains the termNumber list if it fits in the 4 * bytes of an integer. If the first byte in the integer is 1, the next 3 bytes * are a pointer into a byte[] where the termNumber list starts. * * There are actually 256 byte arrays, to compensate for the fact that the pointers * into the byte arrays are only 3 bytes long. The correct byte array for a document * is a function of its id. * * To save space and speed up faceting, any term that matches enough documents will * not be un-inverted... it will be skipped while building the un-inverted field structure, * and will use a set intersection method during faceting. * * To further save memory, the terms (the actual string values) are not all stored in * memory, but a TermIndex is used to convert term numbers to term values only * for the terms needed after faceting has completed. Only every 128th term value * is stored, along with its corresponding term number, and this is used as an * index to find the closest term and iterate until the desired number is hit (very * much like Lucene's own internal term index). * */ public class DocTermOrds implements Accountable { // Term ords are shifted by this, internally, to reserve // values 0 (end term) and 1 (index is a pointer into byte array) private final static int TNUM_OFFSET = 2; /** Every 128th term is indexed, by default. */ public final static int DEFAULT_INDEX_INTERVAL_BITS = 7; // decrease to a low number like 2 for testing private int indexIntervalBits; private int indexIntervalMask; private int indexInterval; /** Don't uninvert terms that exceed this count. */ protected final int maxTermDocFreq; /** Field we are uninverting. */ protected final String field; /** Number of terms in the field. */ protected int numTermsInField; /** Total number of references to term numbers. */ protected long termInstances; private long memsz; /** Total time to uninvert the field. */ protected int total_time; /** Time for phase1 of the uninvert process. */ protected int phase1_time; /** Holds the per-document ords or a pointer to the ords. */ protected int[] index; /** Holds term ords for documents. */ protected byte[][] tnums = new byte[256][]; /** Total bytes (sum of term lengths) for all indexed terms.*/ protected long sizeOfIndexedStrings; /** Holds the indexed (by default every 128th) terms. */ protected BytesRef[] indexedTermsArray = new BytesRef[0]; /** If non-null, only terms matching this prefix were * indexed. */ protected BytesRef prefix; /** Ordinal of the first term in the field, or 0 if the * {@link PostingsFormat} does not implement {@link * TermsEnum#ord}. */ protected int ordBase; /** Used while uninverting. */ protected PostingsEnum postingsEnum; /** If true, check and throw an exception if the field has docValues enabled. * Normally, docValues should be used in preference to DocTermOrds. */ protected boolean checkForDocValues = true; /** Returns total bytes used. */ public long ramBytesUsed() { // can cache the mem size since it shouldn't change if (memsz!=0) return memsz; long sz = 8*8 + 32; // local fields if (index != null) sz += index.length * 4; if (tnums!=null) { for (byte[] arr : tnums) if (arr != null) sz += arr.length; } memsz = sz; return sz; } /** Inverts all terms */ public DocTermOrds(LeafReader reader, Bits liveDocs, String field) throws IOException { this(reader, liveDocs, field, null, Integer.MAX_VALUE); } // TODO: instead of all these ctors and options, take termsenum! /** Inverts only terms starting w/ prefix */ public DocTermOrds(LeafReader reader, Bits liveDocs, String field, BytesRef termPrefix) throws IOException { this(reader, liveDocs, field, termPrefix, Integer.MAX_VALUE); } /** Inverts only terms starting w/ prefix, and only terms * whose docFreq (not taking deletions into account) is * <= maxTermDocFreq */ public DocTermOrds(LeafReader reader, Bits liveDocs, String field, BytesRef termPrefix, int maxTermDocFreq) throws IOException { this(reader, liveDocs, field, termPrefix, maxTermDocFreq, DEFAULT_INDEX_INTERVAL_BITS); } /** Inverts only terms starting w/ prefix, and only terms * whose docFreq (not taking deletions into account) is * <= maxTermDocFreq, with a custom indexing interval * (default is every 128nd term). */ public DocTermOrds(LeafReader reader, Bits liveDocs, String field, BytesRef termPrefix, int maxTermDocFreq, int indexIntervalBits) throws IOException { this(field, maxTermDocFreq, indexIntervalBits); uninvert(reader, liveDocs, termPrefix); } /** Subclass inits w/ this, but be sure you then call * uninvert, only once */ protected DocTermOrds(String field, int maxTermDocFreq, int indexIntervalBits) { //System.out.println("DTO init field=" + field + " maxTDFreq=" + maxTermDocFreq); this.field = field; this.maxTermDocFreq = maxTermDocFreq; this.indexIntervalBits = indexIntervalBits; indexIntervalMask = 0xffffffff >>> (32-indexIntervalBits); indexInterval = 1 << indexIntervalBits; } /** * Returns a TermsEnum that implements ord, or null if no terms in field. * <p> * we build a "private" terms * index internally (WARNING: consumes RAM) and use that * index to implement ord. This also enables ord on top * of a composite reader. The returned TermsEnum is * unpositioned. This returns null if there are no terms. * </p> * <p><b>NOTE</b>: you must pass the same reader that was * used when creating this class */ public TermsEnum getOrdTermsEnum(LeafReader reader) throws IOException { // NOTE: see LUCENE-6529 before attempting to optimize this method to // return a TermsEnum directly from the reader if it already supports ord(). assert null != indexedTermsArray; if (0 == indexedTermsArray.length) { return null; } else { return new OrdWrappedTermsEnum(reader); } } /** * Returns the number of terms in this field */ public int numTerms() { return numTermsInField; } /** * Returns {@code true} if no terms were indexed. */ public boolean isEmpty() { return index == null; } /** Subclass can override this */ protected void visitTerm(TermsEnum te, int termNum) throws IOException { } /** Invoked during {@link #uninvert(org.apache.lucene.index.LeafReader,Bits,BytesRef)} * to record the document frequency for each uninverted * term. */ protected void setActualDocFreq(int termNum, int df) throws IOException { } /** Call this only once (if you subclass!) */ protected void uninvert(final LeafReader reader, Bits liveDocs, final BytesRef termPrefix) throws IOException { final FieldInfo info = reader.getFieldInfos().fieldInfo(field); if (checkForDocValues && info != null && info.getDocValuesType() != DocValuesType.NONE) { throw new IllegalStateException("Type mismatch: " + field + " was indexed as " + info.getDocValuesType()); } //System.out.println("DTO uninvert field=" + field + " prefix=" + termPrefix); final long startTime = System.nanoTime(); prefix = termPrefix == null ? null : BytesRef.deepCopyOf(termPrefix); final int maxDoc = reader.maxDoc(); final int[] index = new int[maxDoc]; // immediate term numbers, or the index into the byte[] representing the last number final int[] lastTerm = new int[maxDoc]; // last term we saw for this document final byte[][] bytes = new byte[maxDoc][]; // list of term numbers for the doc (delta encoded vInts) final Terms terms = reader.terms(field); if (terms == null) { // No terms return; } final TermsEnum te = terms.iterator(); final BytesRef seekStart = termPrefix != null ? termPrefix : new BytesRef(); //System.out.println("seekStart=" + seekStart.utf8ToString()); if (te.seekCeil(seekStart) == TermsEnum.SeekStatus.END) { // No terms match return; } // For our "term index wrapper" final List<BytesRef> indexedTerms = new ArrayList<>(); final PagedBytes indexedTermsBytes = new PagedBytes(15); // we need a minimum of 9 bytes, but round up to 12 since the space would // be wasted with most allocators anyway. byte[] tempArr = new byte[12]; // // enumerate all terms, and build an intermediate form of the un-inverted field. // // During this intermediate form, every document has a (potential) byte[] // and the int[maxDoc()] array either contains the termNumber list directly // or the *end* offset of the termNumber list in its byte array (for faster // appending and faster creation of the final form). // // idea... if things are too large while building, we could do a range of docs // at a time (but it would be a fair amount slower to build) // could also do ranges in parallel to take advantage of multiple CPUs // OPTIONAL: remap the largest df terms to the lowest 128 (single byte) // values. This requires going over the field first to find the most // frequent terms ahead of time. int termNum = 0; postingsEnum = null; // Loop begins with te positioned to first term (we call // seek above): for (;;) { final BytesRef t = te.term(); if (t == null || (termPrefix != null && !StringHelper.startsWith(t, termPrefix))) { break; } //System.out.println("visit term=" + t.utf8ToString() + " " + t + " termNum=" + termNum); visitTerm(te, termNum); if ((termNum & indexIntervalMask) == 0) { // Index this term sizeOfIndexedStrings += t.length; BytesRef indexedTerm = new BytesRef(); indexedTermsBytes.copy(t, indexedTerm); // TODO: really should 1) strip off useless suffix, // and 2) use FST not array/PagedBytes indexedTerms.add(indexedTerm); } final int df = te.docFreq(); if (df <= maxTermDocFreq) { postingsEnum = te.postings(postingsEnum, PostingsEnum.NONE); // dF, but takes deletions into account int actualDF = 0; for (;;) { int doc = postingsEnum.nextDoc(); if (doc == DocIdSetIterator.NO_MORE_DOCS) { break; } //System.out.println(" chunk=" + chunk + " docs"); actualDF ++; termInstances++; //System.out.println(" docID=" + doc); // add TNUM_OFFSET to the term number to make room for special reserved values: // 0 (end term) and 1 (index into byte array follows) int delta = termNum - lastTerm[doc] + TNUM_OFFSET; lastTerm[doc] = termNum; int val = index[doc]; if ((val & 0xff)==1) { // index into byte array (actually the end of // the doc-specific byte[] when building) int pos = val >>> 8; int ilen = vIntSize(delta); byte[] arr = bytes[doc]; int newend = pos+ilen; if (newend > arr.length) { // We avoid a doubling strategy to lower memory usage. // this faceting method isn't for docs with many terms. // In hotspot, objects have 2 words of overhead, then fields, rounded up to a 64-bit boundary. // TODO: figure out what array lengths we can round up to w/o actually using more memory // (how much space does a byte[] take up? Is data preceded by a 32 bit length only? // It should be safe to round up to the nearest 32 bits in any case. int newLen = (newend + 3) & 0xfffffffc; // 4 byte alignment byte[] newarr = new byte[newLen]; System.arraycopy(arr, 0, newarr, 0, pos); arr = newarr; bytes[doc] = newarr; } pos = writeInt(delta, arr, pos); index[doc] = (pos<<8) | 1; // update pointer to end index in byte[] } else { // OK, this int has data in it... find the end (a zero starting byte - not // part of another number, hence not following a byte with the high bit set). int ipos; if (val==0) { ipos=0; } else if ((val & 0x0000ff80)==0) { ipos=1; } else if ((val & 0x00ff8000)==0) { ipos=2; } else if ((val & 0xff800000)==0) { ipos=3; } else { ipos=4; } //System.out.println(" ipos=" + ipos); int endPos = writeInt(delta, tempArr, ipos); //System.out.println(" endpos=" + endPos); if (endPos <= 4) { //System.out.println(" fits!"); // value will fit in the integer... move bytes back for (int j=ipos; j<endPos; j++) { val |= (tempArr[j] & 0xff) << (j<<3); } index[doc] = val; } else { // value won't fit... move integer into byte[] for (int j=0; j<ipos; j++) { tempArr[j] = (byte)val; val >>>=8; } // point at the end index in the byte[] index[doc] = (endPos<<8) | 1; bytes[doc] = tempArr; tempArr = new byte[12]; } } } setActualDocFreq(termNum, actualDF); } termNum++; if (te.next() == null) { break; } } numTermsInField = termNum; long midPoint = System.nanoTime(); if (termInstances == 0) { // we didn't invert anything // lower memory consumption. tnums = null; } else { this.index = index; // // transform intermediate form into the final form, building a single byte[] // at a time, and releasing the intermediate byte[]s as we go to avoid // increasing the memory footprint. // for (int pass = 0; pass<256; pass++) { byte[] target = tnums[pass]; int pos=0; // end in target; if (target != null) { pos = target.length; } else { target = new byte[4096]; } // loop over documents, 0x00ppxxxx, 0x01ppxxxx, 0x02ppxxxx // where pp is the pass (which array we are building), and xx is all values. // each pass shares the same byte[] for termNumber lists. for (int docbase = pass<<16; docbase<maxDoc; docbase+=(1<<24)) { int lim = Math.min(docbase + (1<<16), maxDoc); for (int doc=docbase; doc<lim; doc++) { //System.out.println(" pass=" + pass + " process docID=" + doc); int val = index[doc]; if ((val&0xff) == 1) { int len = val >>> 8; //System.out.println(" ptr pos=" + pos); index[doc] = (pos<<8)|1; // change index to point to start of array if ((pos & 0xff000000) != 0) { // we only have 24 bits for the array index throw new IllegalStateException("Too many values for UnInvertedField faceting on field "+field); } byte[] arr = bytes[doc]; /* for(byte b : arr) { //System.out.println(" b=" + Integer.toHexString((int) b)); } */ bytes[doc] = null; // IMPORTANT: allow GC to avoid OOM if (target.length <= pos + len) { int newlen = target.length; /*** we don't have to worry about the array getting too large * since the "pos" param will overflow first (only 24 bits available) if ((newlen<<1) <= 0) { // overflow... newlen = Integer.MAX_VALUE; if (newlen <= pos + len) { throw new SolrException(400,"Too many terms to uninvert field!"); } } else { while (newlen <= pos + len) newlen<<=1; // doubling strategy } ****/ while (newlen <= pos + len) newlen<<=1; // doubling strategy byte[] newtarget = new byte[newlen]; System.arraycopy(target, 0, newtarget, 0, pos); target = newtarget; } System.arraycopy(arr, 0, target, pos, len); pos += len + 1; // skip single byte at end and leave it 0 for terminator } } } // shrink array if (pos < target.length) { byte[] newtarget = new byte[pos]; System.arraycopy(target, 0, newtarget, 0, pos); target = newtarget; } tnums[pass] = target; if ((pass << 16) > maxDoc) break; } } indexedTermsArray = indexedTerms.toArray(new BytesRef[indexedTerms.size()]); long endTime = System.nanoTime(); total_time = (int) TimeUnit.MILLISECONDS.convert(endTime-startTime, TimeUnit.NANOSECONDS); phase1_time = (int) TimeUnit.MILLISECONDS.convert(midPoint-startTime, TimeUnit.NANOSECONDS); } /** Number of bytes to represent an unsigned int as a vint. */ private static int vIntSize(int x) { if ((x & (0xffffffff << (7*1))) == 0 ) { return 1; } if ((x & (0xffffffff << (7*2))) == 0 ) { return 2; } if ((x & (0xffffffff << (7*3))) == 0 ) { return 3; } if ((x & (0xffffffff << (7*4))) == 0 ) { return 4; } return 5; } // todo: if we know the size of the vInt already, we could do // a single switch on the size private static int writeInt(int x, byte[] arr, int pos) { int a; a = (x >>> (7*4)); if (a != 0) { arr[pos++] = (byte)(a | 0x80); } a = (x >>> (7*3)); if (a != 0) { arr[pos++] = (byte)(a | 0x80); } a = (x >>> (7*2)); if (a != 0) { arr[pos++] = (byte)(a | 0x80); } a = (x >>> (7*1)); if (a != 0) { arr[pos++] = (byte)(a | 0x80); } arr[pos++] = (byte)(x & 0x7f); return pos; } /** * "wrap" our own terms index around the original IndexReader. * Only valid if there are terms for this field rom the original reader */ private final class OrdWrappedTermsEnum extends TermsEnum { private final TermsEnum termsEnum; private BytesRef term; private long ord = -indexInterval-1; // force "real" seek public OrdWrappedTermsEnum(LeafReader reader) throws IOException { assert indexedTermsArray != null; assert 0 != indexedTermsArray.length; termsEnum = reader.fields().terms(field).iterator(); } @Override public PostingsEnum postings(PostingsEnum reuse, int flags) throws IOException { return termsEnum.postings(reuse, flags); } @Override public BytesRef term() { return term; } @Override public BytesRef next() throws IOException { if (++ord < 0) { ord = 0; } if (termsEnum.next() == null) { term = null; return null; } return setTerm(); // this is extra work if we know we are in bounds... } @Override public int docFreq() throws IOException { return termsEnum.docFreq(); } @Override public long totalTermFreq() throws IOException { return termsEnum.totalTermFreq(); } @Override public long ord() { return ordBase + ord; } @Override public SeekStatus seekCeil(BytesRef target) throws IOException { // already here if (term != null && term.equals(target)) { return SeekStatus.FOUND; } int startIdx = Arrays.binarySearch(indexedTermsArray, target); if (startIdx >= 0) { // we hit the term exactly... lucky us! TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(target); assert seekStatus == TermsEnum.SeekStatus.FOUND; ord = startIdx << indexIntervalBits; setTerm(); assert term != null; return SeekStatus.FOUND; } // we didn't hit the term exactly startIdx = -startIdx-1; if (startIdx == 0) { // our target occurs *before* the first term TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(target); assert seekStatus == TermsEnum.SeekStatus.NOT_FOUND; ord = 0; setTerm(); assert term != null; return SeekStatus.NOT_FOUND; } // back up to the start of the block startIdx--; if ((ord >> indexIntervalBits) == startIdx && term != null && term.compareTo(target) <= 0) { // we are already in the right block and the current term is before the term we want, // so we don't need to seek. } else { // seek to the right block TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(indexedTermsArray[startIdx]); assert seekStatus == TermsEnum.SeekStatus.FOUND; ord = startIdx << indexIntervalBits; setTerm(); assert term != null; // should be non-null since it's in the index } while (term != null && term.compareTo(target) < 0) { next(); } if (term == null) { return SeekStatus.END; } else if (term.compareTo(target) == 0) { return SeekStatus.FOUND; } else { return SeekStatus.NOT_FOUND; } } @Override public void seekExact(long targetOrd) throws IOException { int delta = (int) (targetOrd - ordBase - ord); //System.out.println(" seek(ord) targetOrd=" + targetOrd + " delta=" + delta + " ord=" + ord + " ii=" + indexInterval); if (delta < 0 || delta > indexInterval) { final int idx = (int) (targetOrd >>> indexIntervalBits); final BytesRef base = indexedTermsArray[idx]; //System.out.println(" do seek term=" + base.utf8ToString()); ord = idx << indexIntervalBits; delta = (int) (targetOrd - ord); final TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(base); assert seekStatus == TermsEnum.SeekStatus.FOUND; } else { //System.out.println("seek w/in block"); } while (--delta >= 0) { BytesRef br = termsEnum.next(); if (br == null) { assert false; return; } ord++; } setTerm(); assert term != null; } private BytesRef setTerm() throws IOException { term = termsEnum.term(); //System.out.println(" setTerm() term=" + term.utf8ToString() + " vs prefix=" + (prefix == null ? "null" : prefix.utf8ToString())); if (prefix != null && !StringHelper.startsWith(term, prefix)) { term = null; } return term; } } /** Returns the term ({@link BytesRef}) corresponding to * the provided ordinal. */ public BytesRef lookupTerm(TermsEnum termsEnum, int ord) throws IOException { termsEnum.seekExact(ord); return termsEnum.term(); } /** Returns a SortedSetDocValues view of this instance */ public SortedSetDocValues iterator(LeafReader reader) throws IOException { if (isEmpty()) { return DocValues.emptySortedSet(); } else { return new LegacySortedSetDocValuesWrapper(new Iterator(reader), reader.maxDoc()); } } private class Iterator extends LegacySortedSetDocValues { final LeafReader reader; final TermsEnum te; // used internally for lookupOrd() and lookupTerm() // currently we read 5 at a time (using the logic of the old iterator) final int buffer[] = new int[5]; int bufferUpto; int bufferLength; private int tnum; private int upto; private byte[] arr; Iterator(LeafReader reader) throws IOException { this.reader = reader; this.te = termsEnum(); } @Override public long nextOrd() { while (bufferUpto == bufferLength) { if (bufferLength < buffer.length) { return NO_MORE_ORDS; } else { bufferLength = read(buffer); bufferUpto = 0; } } return buffer[bufferUpto++]; } /** Buffer must be at least 5 ints long. Returns number * of term ords placed into buffer; if this count is * less than buffer.length then that is the end. */ int read(int[] buffer) { int bufferUpto = 0; if (arr == null) { // code is inlined into upto //System.out.println("inlined"); int code = upto; int delta = 0; for (;;) { delta = (delta << 7) | (code & 0x7f); if ((code & 0x80)==0) { if (delta==0) break; tnum += delta - TNUM_OFFSET; buffer[bufferUpto++] = ordBase+tnum; //System.out.println(" tnum=" + tnum); delta = 0; } code >>>= 8; } } else { // code is a pointer for(;;) { int delta = 0; for(;;) { byte b = arr[upto++]; delta = (delta << 7) | (b & 0x7f); //System.out.println(" cycle: upto=" + upto + " delta=" + delta + " b=" + b); if ((b & 0x80) == 0) break; } //System.out.println(" delta=" + delta); if (delta == 0) break; tnum += delta - TNUM_OFFSET; //System.out.println(" tnum=" + tnum); buffer[bufferUpto++] = ordBase+tnum; if (bufferUpto == buffer.length) { break; } } } return bufferUpto; } @Override public void setDocument(int docID) { tnum = 0; final int code = index[docID]; if ((code & 0xff)==1) { // a pointer upto = code>>>8; //System.out.println(" pointer! upto=" + upto); int whichArray = (docID >>> 16) & 0xff; arr = tnums[whichArray]; } else { //System.out.println(" inline!"); arr = null; upto = code; } bufferUpto = 0; bufferLength = read(buffer); } @Override public BytesRef lookupOrd(long ord) { try { return DocTermOrds.this.lookupTerm(te, (int) ord); } catch (IOException e) { throw new RuntimeException(e); } } @Override public long getValueCount() { return numTerms(); } @Override public long lookupTerm(BytesRef key) { try { switch (te.seekCeil(key)) { case FOUND: assert te.ord() >= 0; return te.ord(); case NOT_FOUND: assert te.ord() >= 0; return -te.ord()-1; default: /* END */ return -numTerms()-1; } } catch (IOException e) { throw new RuntimeException(e); } } @Override public TermsEnum termsEnum() { try { return getOrdTermsEnum(reader); } catch (IOException e) { throw new RuntimeException(e); } } } }